JAVA基础知识--线程

发布于:2022-12-18 ⋅ 阅读:(183) ⋅ 点赞:(0)

进程

  计算机同一时刻只能有一个进程
  CPU分配时间片
  CPU通过一个指针,在打开的进程里,非常迅速的轮转,所以可以边听音乐,边敲代码,边看ppt
  进程是由开发人员设置的
  进程就是正在进行的程序
  		独立性:系统中独立存在的实体,进程之间相互独立
  		动态性:程序是一个静态的指令集和,进程是一个正在系统中活动的指令集和,进程中加入时间概念,进程具有自己的生命周期和各种不同的状态,是程序中不具备的
  		并发性:多个进程可以在单个处理器并发执行,多个进程之间不会互相影响
  并行:指在同一时刻,有多条指令在处理器上同时执行,宏观和微观都是同时的
  并发:指在同一时刻只能有一条指令执行,因为指针的迅速轮转,宏观上认为是同时的

线程

  线程是进程中的顺序执行控制流,是一条执行路径,是操作系统能够进行运算调度的最小单位	
  简而言之:一个程序运行至少一个进程,一个进程包含一个线程(单线程)或多个线程
  线程依赖进程存在
  Thread类,JAVASE中规定,一个类只要继承Thread类,此类就是一个线程类
  子类中必须重写父类Thread类的run()方法,此方法为线程的主体,也就是当线程被CPU调度时要执行的方法
  如果无法使用this获取线程名称,可以使用currentThread获取
  可设置CPU的优先级
  线程的优先级并不能保证线程的执行顺序,只是CPU给优先级高的线程的资源概率大
  优先级低的也不是没有机会执行

生命周期

  新建状态(New):
  		当一个线程对象被创建后,线程就处于新建状态。
  		在新建状态中的线程对象从严格意义上看还只是一个普通的对象,还不是一个独立的线程,不会被线程调度程序调度。
  		新建状态是线程生命周期的第一个状态。
  就绪状态 (Runnable):
  		处于新建状态中的线程被调用start()方法就会进入就绪状态。
  		处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,但并不是说执行了start()方法线程就会立即执行。
  		在等待/阻塞状态中的线程,被解除等待和阻塞后将不直接进入运行状态,而是先进入就绪状态
  运行状态(Running):
  		处于就绪状态中的线程一旦被系统选中,使线程获取了CPU时间,就会进入运行状态。
  		线程在运行状态下随时都可能被调度程序调度回就绪状态。在运行状态下还可以让线程进入到等待/阻塞状态。
  		在通常的单核CPU中,在同一时刻只有一个线程处于运行状态。在多核的CPU中,就可能两个线程或更多的线程同时处于运行状态,这也是多核CPU运行速度快的原因。
  		注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,必须先处于就绪状态中。
  阻塞状态(Blocked):
  		根据阻塞产生的原因不同,阻塞状态又可以分为三种
  		等待阻塞:运行状态中的线程执行wait()方法,使当前线程进入到等待阻塞状态;		
  		锁阻塞:线程在获取synchronized同步锁失败(因为锁被其它线程所占用),线程会进入同步阻塞状态。
  		其他阻塞:通过调用线程的sleep()或发出了I/O请求时等,线程会进入到阻塞状态。当sleep()睡眠结束或I/O处理完毕时,线程重新转入就绪状态。

继承Thread类

public class XianCheng {
    public static void main(String[] args) {//单线程
     	//4创建两个线程对象,
        Thread01 t1=new Thread01();
        Thread01 t2=new Thread01();
        //5启动线程
        //t1.run();//可执行,还是单线程,
        //t2.run();//同上,和调用普通方法没区别,不会启动线程

        //必须调用start()方法才能启动线程,然后由JVM调用这个线程来执行线程的run()方法
        t1.start();
        t2.start();
        
        Thread02 t3=new Thread02();
        Thread02 t4=new Thread02();
        Thread02 t5=new Thread02();
        t3.setName("t3");
        t4.setName("t4");
        t5.setName("t5");
        //获取优先级
        System.out.println(t3.getPriority());
        System.out.println(t4.getPriority());
        System.out.println(t5.getPriority());
        t3.setPriority(1);
        t4.setPriority(5);
        t5.setPriority(10);
        //t5优先级最高,t4次高,t3最低
        t3.start();
        t4.start();
        t5.start();
    }
}
//1定义一个类继承Thread类,该类就是一个线程类
class Thread01 extends Thread{
    //2重写Thread类中的run()方法
    @Override
    public void run() {
        //3编写业务代码
        for (int i = 0; i < 100; i++) {
            //this就是指当前调用的对象,获取线程名称
            System.out.println(this.getName()+"run方法执行" +i);
        }
    }
}
class Thread02 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 101; i++) {
            System.out.println(this.getName()+"线程执行run()方法"+i);
        }
    }
}

实现Runnable接口

  Thread类的局限性,猫类继承了动物类,就不能继承Thread类
  为此,Thread提供了两个构造方法
  		→使用Runnable接口实现多线程
  		→Runnable接口中没有start方法,依赖于Thread启动线程
  好处:继承其他类没有限制
public class XianCheng02 {
    public static void main(String[] args) {
        //4.创建线程类Runnable01对象(他不是线程对象,而是线程对象的执行目标对象)
        Runnable01 target=new Runnable01();
        //5.创建Thread类的对象,将目标对象作为参数传入,作为执行目标,
        // 目的为了启用多个线程
        //真正意义上的线程对象,target是线程对象的执行目标对象
        Thread t1=new Thread(target,"t1");//第二个参数为线程名称
        Thread t2=new Thread(target,"t2");
        //6.启动线程
        t1.start();
        t2.start();
    }
}
//1.创建一个类,实现Runnable接口
class Runnable01 implements Runnable{
    //2.重写Runnable接口中的run方法
    @Override
    public void run() {
        //3.业务代码
        for (int i = 0; i < 101; i++) {
            System.out.println(Thread.currentThread().getName()+"线程执行了run()"+i);
        }
    }
}

线程案例

public class XCAnLi {
    public static void main(String[] args) {
      Ticket t1=new Ticket();
      t1.setName("窗口1");
      Ticket t2=new Ticket();
      t2.setName("窗口2");
      Ticket t3=new Ticket();
      t3.setName("窗口3");
      t1.start();
      t2.start();
      t3.start();
    }
}
//通过继承Thread类
class Ticket extends Thread {
    //不加static就不是随着类而加载,会出现重复卖票的情况,各自持有100张
    static int tickets=100;
    @Override
    public void run() {
        while (true) {
            if (tickets > 0) {
                System.out.println(this.getName() + "卖出了第" + (tickets--) + "张票");
            } else {
                break;
            }
        }
    }
}
public class XCAnLi {
    public static void main(String[] args) {
        Tickets t=new Tickets();
        Thread t1=new Thread(t,"窗口1");
        Thread t2=new Thread(t,"窗口2");
        Thread t3=new Thread(t,"窗口3");
        t1.start();
        t2.start();
        t3.start();
    }
}
//通过实现Runnable类
class Tickets implements Runnable{
    //不用static修饰,因为目标对象只有一个,所以不会影响
    int tickets=100;
    @Override
    public void run() {
        while (true){
            if(tickets>0){
                System.out.println(Thread.currentThread().getName()+"卖出了第"+(tickets--)+"张票");
            }else {
                break;
            }
        }
    }
}

线程安全

  线程安全问题:
  		例如上边的案例可能出现一票多卖或者负数情况。多次运行有可能出现问题
  判断一个程序是否有线程安全问题依据:
  		是否存在多线程环境
  		是否存在共享数据
  		是否存在多条语句访问,修改共享数据
  为了解决这样的问题就使用到了:同步代码块
  同步代码块是指一个时间段内只能有一个线程执行,提供synchronized关键字
  将有可能发生线程安全问题的代码包含在同步代码块中,同一时间只允许一个线程进入同步代码块
  必须指定一个需要同步的对象,也称为锁对象,这里的锁对象,可以是任意对象,但必须只能是一个
  若使用this作为锁对象,需保证多个线程执行时,this指向的是同一个对象
  也可以使用关键字将一个方法修饰成同步方法,它能实现和同步代码块同样的功能
  访问该方法的其他线程都会发生阻塞,直到当前线程访问完毕,其他线程才有机会访问

同步代码块

  synchronized 关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问
  格式:synchronized (锁对象) {需要同步的代码}
  		锁对象可以是任意类型。
   		多个线程对象要使用同一把锁。
//还是使用售票案例
public class XCAQ {
    public static void main(String[] args) {
        Tickets2 t=new Tickets2();
        Thread t1=new Thread(t,"窗口1");
        Thread t2=new Thread(t,"窗口2");
        Thread t3=new Thread(t,"窗口3");
        t1.start();
        t2.start();
        t3.start();
    }
}
class Tickets2 implements Runnable {
    //不用static修饰,因为目标对象只有一个,所以不会影响
    int tickets = 100;
    /*
    当不可以使用this时,可先创建object对象
    ※static int tickets=100;
    ※static Object obj=new Object();
    synchronized (obj){}
    ※两行都必须是类的,使用static修饰
     */
    @Override
    public void run() {
        while (true) {
            synchronized (this) {//this指目标对象
            /*
            当前类使用this没问题,因为只有一个目标对象,
            当直接使用Thread类,有多个对象时,则不要使用this,因为this表示的锁对象有多个
             */
                if (tickets > 0) {
                    try {
                        Thread.sleep(200);
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "卖出了第" + (tickets--) + "张票");
                } else {
                    break;
                }
            }
        }
    }
}

同步方法

  用synchronized修饰的方法叫做同步方法,保证一个线程执行该方法的时候,其他线程只能在方法外等着。
  格式:public synchronized void method(){可能会产生线程安全问题的代码}
public class XCAQ2 {
    public static void main(String[] args) {
        Tickets4 t1=new Tickets4();
        t1.setName("窗口1");
        Tickets4 t2=new Tickets4();
        t2.setName("窗口2");
        Ticket t3=new Ticket();
        t3.setName("窗口3");
        t1.start();
        t2.start();
        t3.start();
    }
}
class Tickets4 extends Thread {
    static int tickets = 100;
    @Override
    public void run() {
        while (true) {
            setSyncObj();
            if(tickets<=0){
                break;
            }
        }
    }
    public static synchronized void setSyncObj(){//随着类创建而创建,锁对象只有一个,this
        if (tickets > 0) {
            try {
                Thread.sleep(200);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "卖出了第" + (tickets--) + "张票");
        }
    }
}

Lock锁

  为了更清晰的表达如何加锁和释放锁,jdk5以后提供了一个新的锁对象Lock
  比使用synchronized方法和语句获得更广泛的锁定操作
  提供了获得锁和释放锁的方法
  		void lock():获得锁
  		void unlock():释放锁
  Lock是不能直接实例化,采用实现ReentrantLock来实例化
  睡眠sleep()方法: 让当前线程进入到睡眠状态,到指定毫秒后自动醒来继续执行
public class LockDemo {
    public static void main(String[] args) {
        Tickets5 t=new Tickets5();
        Thread t1=new Thread(t,"窗口1");
        Thread t2=new Thread(t,"窗口2");
        Thread t3=new Thread(t,"窗口3");
        t1.start();
        t2.start();
        t3.start();
    }
}
class Tickets5 implements Runnable {
    static int tickets = 100;
    private ReentrantLock lock=new ReentrantLock();
    @Override
    public void run() {
        while (true) {
            try {
                lock.lock();
                if (tickets > 0) {
                    //此时如果出现异常,也未释放锁
                 	Thread.sleep(200);
                    System.out.println(Thread.currentThread().getName() + "卖出了第" + (tickets--) + "张票");
                } else {
                    break;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
            //将释放锁放在finally代码块中,就出现异常还是其他情况,释放锁都会被执行
                lock.unlock();
            }
        }
    }
}

死锁

  死锁:线程死锁是指由于两个或多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行
  线程嵌套会出现死锁情况
  下面案例就是死锁现象
public class LockDemo {
    public static void main(String[] args) {
        Object obj1=new Object();
        Object obj2=new Object();
        new Thread(()->{
            while(true){
                synchronized (obj1){
                    //线程1
                    synchronized (obj2) {
                        System.out.println("tom run");
                    }
                }
            }
        }).start();
        new Thread(()->{
            while(true){
                synchronized (obj2){
                    //线程2
                    synchronized (obj1) {
                        System.out.println("jack run");
                    }
                }
            }
        }).start();
    }
}
/*执行结果,没有结束,但处于死锁中
tom run
tom run
tom run
tom run
tom run
tom run
tom run
tom run
tom run
tom run
tom run
jack run
*/
/*
//MyThread类操作等同于第一个new Thread(()->{})
class MyThread extends Thread{
    Object obj1=new Object();
    Object obj2=new Object();
    @Override
    public void run() {
        while(true){
            synchronized (obj1){
                synchronized (obj2) {
                    System.out.println("tom run");
                }
            }
        }
    }
}*/

线程状态

锁中有提到睡眠方法

等待和唤醒

  void wait():导致当前线程等待,直到另一个线程调用该对象的唤醒方法
  void notify():唤醒正在等待对象监视器的单个线程
  void notifyAll():唤醒正在等待对象监视器的所有线程
//小案例来实现
 /*				消费者,桌子,生产者
消费者步骤:									生产者步骤:
1.判断桌子上是否有汉堡包						1.判断桌子上是否有汉堡包
2.如集没有就等待								如果有就等待。如果没有才生产。
3.如果有就开吃								2.把汉堡包放在桌子上。
4.吃完之后,桌子上的汉堡包就没有了,			3.叫醒等待的消费者开吃
	叫醒等待的生产者继续生产
议堡包的总数量减—
     */
     public class WaitNotify {
    public static void main(String[] args) {
        customer c=new customer();
        Producer p=new Producer();
        c.start();
        p.start();
    }
}
class customer extends Thread{
    @Override
    public void run() {
        while (true){
            synchronized (Desk.lockobj){
                if(Desk.count==0){
                    break;
                }else {
                    if (Desk.flag){
                        //桌子上有面包
                        System.out.println("消费者在消费面包");
                        Desk.flag=false;
                        Desk.lockobj.notifyAll();
                        System.out.println("消费者吃了第"+Desk.count+"个面包");
                        Desk.count--;
                    }else {
                        //桌子上没有面包
                        //使用哪个对象当作锁,就必须用这个对象调用唤醒和等待
                        try {
                            Desk.lockobj.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }
}
class Producer extends Thread{
    @Override
    public void run() {
        while (true){
            synchronized (Desk.lockobj){
                if(Desk.count==0){
                    break;
                }else {
                    if (!Desk.flag){
                        //桌子上没有面包就生产
                        System.out.println("生产者开始生产面包");
                        Desk.flag=true;
                        Desk.lockobj.notifyAll();
                    }else {
                        //桌子上有面包就等待
                        try {
                            Desk.lockobj.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }
}
class Desk{
    //声明一个标记
    //true表示桌子上有面包,允许消费者执行
    //false表示桌子上没有面包,此时允许厨师执行
    public static boolean flag=false;

    //最大生产数量
    public static int count=15;
    //因为都是围绕桌子进行,所以将锁设在桌子比较合适
    //锁对象
    public static final Object lockobj=new Object();
}

线程池

  线程池:用来装线程
  代码实现:
  1.创建一个空池子,创建Executors中的静态方法
  2.有任务需要执行时,创建线程对象  ---------->submit方法   线程池会自动帮创建对象
  	   任务执行完毕,线程对象归还线程池   ------>^          执行完毕也会自动归还
  创建线程池方式一
  		static ExcutorService newCachedThreadPool():创建一个根据需要创建新线程的线程池,但在他们可用时将重用以前构造的线程
  创建线程池方式二
  		static ExcutorService newFixedThreadPool(int nThreads):创建一个指定最多线程数量的线程池,nThreads表示最多线程数量
//方式一
public class XCpool {
    public static void main(String[] args) throws InterruptedException {
        //创建一个默认的线程池对象,默认为空,默认数量是int类型最大值
        //Executors这个类可以帮助我们创建线程池对象
        //ExecutorService这个类可以控制线程池
        ExecutorService pool = Executors.newCachedThreadPool();
        pool.submit(()->{
            System.out.println(Thread.currentThread().getName()+"1在执行");
        });
        Thread.sleep(3000);
        pool.submit(()->{
            System.out.println(Thread.currentThread().getName()+"2在执行");
        });
        pool.submit(()->{
            System.out.println(Thread.currentThread().getName()+"3在执行");
        });
        pool.shutdown();
    }
}
//方式二
public class ThreadPoolDemo {
    public static void main(String[] args) {
        ExecutorService es = Executors.newFixedThreadPool(10);//是最大值,而不是初始值
        for (int i = 0; i < 20; i++) {
            es.submit(()->{
                System.out.println(Thread.currentThread().getName()+"1在执行");
            });

            es.submit(()->{
                System.out.println(Thread.currentThread().getName()+"2在执行");
            });
            es.submit(()->{
                System.out.println(Thread.currentThread().getName()+"3在执行");
            });
        }

        ThreadPoolExecutor pe=(ThreadPoolExecutor)es;
        System.out.println(pe.getPoolSize());//10,提交10个任务,创建10个线程
        es.shutdown();
    }
}
 /*
    1核心线程数量,2最大线程数量,3空闲线程存活时间,4时间单位,
    5阻塞队列: 让任务在队列中等待,有线程空闲了就会从阻塞队列中获取任务并执行
    6创建线程方式:创建线程对象(Executors.defaultThreadFactory()默认方式)
    7超出任务拒绝策略:
    	1)什么时候拒绝:当提交的任务 > 池子中最大线程数量+队列容量
        2)怎么拒绝:ThreadPoolExecutor.AbortPolicy()是默认的策略,抛出(内部类)表示丢弃任务并抛出
                    ThreadPoolExcutor.DiscardPolicy()丢弃任务,不抛出异常,不推荐的做法(只执行 最大线程数量+阻塞队列)
                    ThreadPoolExcutor.DiscardOldestPolicy:抛弃队列中等待时间最久的任务,然后把当前任务加入队列中
                    ThreadPoolExcutor.CallerRunsPolicy:调用任务的run()方法绕过线程池直接执行
     核心线程数量:        不能小于0
     最大线程数量:        不能小于等于0,数量>=核心线程数量
     空闲线程最大存活时间:  不能小于0
     时间单位:            时间单位
     任务队列:           不能为null
     创建线程工厂:        不能为null
     任务的拒绝策略:      不能为null
     */
    public static void main(String[] args) {  
    //第5项为阻塞队列 //第7项用的是内部类,拒绝处理程序                                                                                 
        ThreadPoolExecutor pe=new ThreadPoolExecutor(1,3,2, TimeUnit.SECONDS,new ArrayBlockingQueue<>(2), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
        //pe.submit(()->{}) 用lambda表达式也可以
        for (int i = 0; i < 5; i++) {//正好
            pe.submit(new RunnableTest());
        }
        pe.shutdown();
        //测试7.2)的第四项
        ThreadPoolExecutor tpe=new ThreadPoolExecutor(1,3,2, TimeUnit.SECONDS,new ArrayBlockingQueue<>(2), Executors.defaultThreadFactory(),new ThreadPoolExecutor.CallerRunsPolicy());
        //pe.submit(()->{}) 用lambda表达式也可以
        for (int i = 0; i < 5; i++) {//正好
            int y=i;
            tpe.submit(()->{
                System.out.println(Thread.currentThread().getName()+" :"+y);
            });
        }
        tpe.shutdown();
    }
}
class RunnableTest implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"线程执行了");
    }
}
本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到