02_06_Java语音进阶||day06_线程、同步、线程状态

第一章 线程

1.1 多线程原理-执行流程

1.2 多线程原理-内存图解

  1. 多线程的好处:多个线程之间互不影响(在不同的栈空间)

1.3 Thread类常用方法_获取线程名称的方法

  1. 获取线程的名称两种方法:
    1. 使用Thread类中的方法getName()
      • String getName()
        • 返回该线程的名称。
    2. 可以先获取当前正在执行的线程,再使用线程中的方法getName()获取线程的名称
      • static ++Thread++ currentThread()
        • 返回对当前正在执行的线程对象的引用。
    //定义一个Thread类的子类:MyThread.java
    public class MyThread extends Thread{
    
        //重写Thread类中的run方法
    
        @Override
        public void run() {
            //1.直接获取线程名称
    //        String name = getName();
    //        System.out.println(name);
    
            //2. 先获取到执行的线程,再获取线程名称
            //Thread s = Thread.currentThread();
            //System.out.println(s);  //Thread[Thread-0,5,main]
            //String name = s.getName();
            //System.out.println(name);   //Thread-0
    
            //链式编程
            String name = Thread.currentThread().getName();
            System.out.println(name);
        }
    }
    
    //主方法:Demo01GetThreadName.java
    /*
    线程名称:
        主线程:main
        新线程:Thread-0,Thread-1
     */
    
    public class Demo01GetThreadName {
        public static void main(String[] args) {
            //创建Thread类的子类对象
            MyThread mt = new MyThread();
    
            //调用start方法,开启线程来执行run方法
            mt.start(); //Thread-0
    
            //创建一个新的线程
            new MyThread().start(); //Thread-1
    
            //通过第二种方法可以获取到主线程的名称(因为主方法没有继承Thread没法使用getName方法)
            System.out.println(Thread.currentThread().getName());   //main
        }
    }

1.4 Thread类常用方法_设置线程名称的方法(了解)

  1. 设置线程的名称:
    1. 使用Thread类中的方法setName(名字)
      • void setName(String name) 改变线程名称,使之与参数 name 相同。
    2. 创建一个带参数的构造方法,参数传递线程的名称;调用父类的带参构造方法,把线程名称传递给父类,让父类(Thread)给子线程起一个名字
      • Thread(String name) 分配新的 Thread 对象。
    //定义一个Thread类的子类:MyThread.java
    public class MyThread extends Thread{

        public MyThread(){
    
        }
    
        public MyThread(String name){
            super(name);
        }
    
        @Override
        public void run() {
            //获取线程名称
            System.out.println(Thread.currentThread().getName());
        }
    }
    
    //主方法:Demo01SetThreadName.java
    public static void main(String[] args) {
        //开启多线程
        MyThread mt = new MyThread();
        //第一种方式
        mt.setName("线程1");
        mt.start();

        //开启第二个线程
        new MyThread("线程2").start();
    }
    
    //结果:
    线程1
    线程2

1.5 Thread类常用方法_sleep

  1. public static void sleep(long millis):
    • 使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。
    • 毫秒数结束之后,线程继续执行
  • 注:静态方法,所以直接使用类名调用
    //主方法:Demo01Sleep.java
    public static void main(String[] args) {
        //模拟秒表
        for(int i = 1; i <= 60; i++){
            System.out.println(i);

            //使用Thread类中的sleep方法让程序睡眠一秒钟
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    //结果是一秒显示一个

1.6 创建多线程程序的第二种方式—实现Runnable接口

  1. 创建多线程程序的第二种方式:实现Runnable接口
    • java.lang.Runnable
      • (API)Runnable—>接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个称为 run 的无参数方法。
    • java.lang.Thread类的构造方法
      • Thread(Runnable target) 分配新的 Thread 对象。
      • Thread(Runnable target, String name) 分配新的 Thread 对象。
  2. 实现步骤:
    1. 创建一个Runnable接口的实现类
    2. 在实现类中重写Runnable接口的run方法,设置线程任务
    3. 创建一个Runnable接口的实现类对象
    4. 创建Thread类对象【!】,构造方法中传递Runnable接口的实现类对象
    5. 调用Thread类中start方法,开启新的线程执行run方法
    //1. 创建一个Runnable接口的实现类:RunnableImpl.java
    public class RunnableImpl implements Runnable{
    
        //2. 在实现类中==重写Runnable接口的run方法==,设置线程任务
        @Override
        public void run() {
            for(int i = 0; i < 20; i ++){
                System.out.println(Thread.currentThread().getName() + "-->" + i);
            }
        }
    }
    
    //主方法:Demo01Runnable.java
    public static void main(String[] args) {

        //3. 创建一个==Runnable接口的实现类对象==
        RunnableImpl runI = new RunnableImpl();
        //4. ==创建Thread类对象==,==构造方法中传递Runnable接口的实现类对象==
        Thread t = new Thread(runI);
        //5. ==调用Thread类中==的==start方法==,开启新的线程执行run方法
        t.start();

        for(int i = 0; i < 20; i ++){
            System.out.println(Thread.currentThread().getName() + "-->" + i);
        }
    }

1.7 Thread类和Runnable接口的区别

  1. 实现Runnable接口创建多线程程序的好处:
    1. 避免了单继承的局限性
      • 一个类只能继承一个类(一个人只能有一个亲爹),类继承了Thread类就不能继承其他的类
      • 实现了Runnable接口,还可以继承其他的类,实现其他的接口
    2. 增强了程序的扩展性,降低了程序的耦合性(解耦)
      • 实现Runnable接口的方式,把设置线程任务开启新线程进行了分离(解耦)
      • 实现类中,重写了run方法:++用来设置线程任务++
      • 创建Thread类对象,调用start方法:++用来开启新线程++
  • 总结:以后使用多线程尽可能使用实现Runnable接口方式【重点】
    //1. 创建一个Runnable接口的实现类:RunnableImpl.java
    public class RunnableImpl implements Runnable{
    
        //2. 在实现类中==重写Runnable接口的run方法==,设置线程任务
        @Override
        public void run() {
            for(int i = 0; i < 20; i ++){
                System.out.println(Thread.currentThread().getName() + "-->" + i);
            }
        }
    }
    
    //2. 创建另一个Runnable接口的实现类:RunnableImpl2.java
    public class RunnableImpl2 implements Runnable{

    
        @Override
        public void run() {
            for(int i = 0; i < 20; i ++){
                System.out.println("HelloWorld");
            }
        }
    }
    
    //主方法:Demo01Runnable.java
    public static void main(String[] args) {

        //3. 创建一个==Runnable接口的实现类对象==
        RunnableImpl runI = new RunnableImpl();
        
        //4. ==创建Thread类对象==,==构造方法中传递Runnable接口的实现类对象==
        //Thread t = new Thread(runI);
        //实现Runnable接口的好处之一:把设置线程任务和开启新线程进行了分离
        Thread t = new Thread(new RunnableImpl2());

        //5. ==调用Thread类中==的==start方法==,开启新的线程执行run方法
        t.start();
        
        for(int i = 0; i < 20; i ++){
            System.out.println(Thread.currentThread().getName() + "-->" + i);
        }
    }

1.8 匿名内部类的方式实现线程的创建

  1. 匿名内部类方法实现线程的创建
    • 匿名:没有名字
    • 内部类:写在其他类内部的类
  2. 匿名内部类作用:简化代码【!】
    • 把子类继承父类,重写父类的方法,创建子类对象合一步完成
    • 把实现类实现类接口,重写接口中的方法,创建实现类对象合成一步完成
  3. 匿名内部类的最终产物【!】:子类/实现类对象,而这个类没有名字
  4. 格式:
        new 父类/接口(){
            重复父类/接口中的方法
        }.start();
    
    //主方法:Demo01InnerClassThread.java
    public static void main(String[] args) {

        //线程的父类是Thread(继承Thread类方式——>匿名内部类)
        new Thread(){
            @Override
            public void run() {
                for(int i = 0; i < 2; i++){
                    System.out.println(Thread.currentThread().getName() + "-->" + "java");
                }
            }
        }.start();


        //线程的接口Runnable(实现Runnable接口方式——>匿名内部类)
        Runnable r = new Runnable(){

            @Override
            public void run() {
                for(int i = 0; i < 2; i++){
                    System.out.println(Thread.currentThread().getName() + "-->" + "程序员");
                }
            }
        };
        //创建Thread对象,构造方法来调用Runnable对象,来开启多线程
        new Thread(r).start();

        //简化接口的方式
        new Thread(new Runnable(){

            @Override
            public void run() {
                for(int i = 0; i < 2; i++){
                    System.out.println(Thread.currentThread().getName() + "-->" + "匿名内部类来实现多线程程序");
                }
            }
        }).start();
    }
    
    //结果:
    Thread-0-->java
    Thread-0-->java
    Thread-2-->匿名内部类来实现多线程程序
    Thread-1-->程序员
    Thread-1-->程序员
    Thread-2-->匿名内部类来实现多线程程序

第二章 线程安全

2.1 线程安全问题概述

2.2 线程安全问题的代码实现

  1. 实现2.1的卖票功能(共享数据)——就会出现线程安全问题
    //定义一个Runnable接口的实现类:RunnableImpl.java
    public class RunnableImpl implements Runnable{
        //定义一个多个线程共享的票源
        private int ticket = 100;
    
        //设置线程任务:卖票
        @Override
        public void run() {
            //使用死循环,让卖票操作重复执行
            while (true){
                //先判断票是否存在
                if(ticket > 0){
                    //提高安全问题出现的概率,让程序睡眠
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
    
    
                    //票存在,卖票,ticket--
                    System.out.println(Thread.currentThread().getName() + "-->正在卖" + ticket + "张票");
                    ticket--;
                }
            }
    
        }
    }
    
    //主方法:Demo01Ticket.java
    /*
    实现卖票案例
        创建3个线程,同时开启,对共享的票进行出售
     */
    public class Demo01Ticket {
        public static void main(String[] args) {
            //创建Runnable接口实现类的对象
            //这里创建一个就只有100张票,三个就变成300张了
            RunnableImpl runR = new RunnableImpl();
            //创建Thread方法,他的构造方法中传递Runnable接口的实现类对象
            Thread t0 = new Thread(runR);
            Thread t1 = new Thread(runR);
            Thread t2 = new Thread(runR);
            //调用start方法来开启线程
            t0.start();
            t1.start();
            t2.start();
        }
    }
    
    //其中部分出现线程安全的结果:
    Thread-1-->正在卖1张票
    Thread-0-->正在卖0张票
    Thread-2-->正在卖-1张票

2.3 线程安全问题产生的原理

  1. 线程安全问题(重复,和出现不存在的值)

  • 注:线程安全问题是不能产生的
    • 我们可以让一个线程在访问共享数据的时候,++无论是否失去了CPU的执行权++;
    • 让其他的线程只能等待,等待当前线程卖完票,其他线程在进行卖票。
  • 总结:每次保证使用一个线程在卖票【重点】

2.4 解决线程安全问题:线程同步

  1. 为了保证每个线程都能正常执行原子操作Java引入了线程同步机制【重点】
  2. 有三种方式完成同步操作
    1. 同步代码块
    2. 同步方法
    3. 锁机制

2.5 解决线程安全问题:线程同步—同步代码块

  1. 格式:
        synchronized(锁对象){
            可能会出现线程安全问题的代码(访问了共享数据的代码)
        }
    
  2. 注意事项:
    1. 通过代码块中的锁对象,可以使用任意的对象
    2. 但是必须保证多个线程使用的锁对象是同一个
    3. 创建的锁对象,一定要在run方法的外面(保证唯一)
  3. 锁对象作用:
    • 把同步代码块锁住,只让一个线程在同步代码块中执行
    //修改2.2的实现类,实现了同步代码块
    public class RunnableImpl implements Runnable{
        //定义一个多个线程共享的票源
        private int ticket = 100;
    
        //创建一个锁对象***
        Object obj = new Object();
    
    
        //设置线程任务:卖票
        @Override
        public void run() {
            while (true){
    
                //同步代码块***
                synchronized(obj){
                    //先判断票是否存在
                    if(ticket > 0){
                        //票存在,卖票,ticket--
                        System.out.println(Thread.currentThread().getName() + "-->正在卖" + ticket + "张票");
                        ticket--;
                    }
                }
            }
    
        }
    }
    
    //这样的结果就不会出现,重复和不存在的票了

2.6 同步技术的原理

  1. 使用了一个锁对象,这个锁对象叫同步锁,也叫对象锁,也叫对象监视器
  2. 例子引入:
    • 3个线程一起抢夺cpu的执行权,谁抢到了谁执行run方法进行卖票
      • t0抢到了cpu的执行权,执行run方法,遇到synchronized代码块,这时t0会检查synchronized代码块是否有锁对象,发现有,就会获取锁对象,进入到同步中执行。
      • t1抢到了cpu的执行权,执行run方法,遇到synchronized代码块,这时t1会检查synchronized代码块是否有锁对象,发现没有,t1就会进入到阻塞状态,会一直等待t0线程归还锁对象,一直到t0线程执行完同步中的代码,会把锁对象归还给同步代码块,t1才能获取到锁对象,进入到同步中执行
  3. 总结:
    • 同步中的线程,没有执行完毕不会释放锁,同步外的线程没有锁进不去同步【重点】
  4. 同步技术的优劣【重点】
    • 优:同步保证了只能有一个线程在同步中执行共享数据
      • 保证了安全
    • 劣:程序频繁的判断锁,获取锁,释放锁
      • 程序的效率会降低

2.7 解决线程安全问题:线程同步—同步方法(普通版本-常用)

  1. 解决线程安全问题的二种方案:使用同步方法
  2. 使用步骤:
    1. 把访问了共享数据的代码抽取出来,放到一个方法中
    2. 在方法上添加synchronized修饰符
  3. 格式:(定义方法的格式)
        修饰符 synchronized 返回值类型 方法名(参数列表){
            可能会出现线程安全问题的代码(访问了共享数据的代码)
        }
    
    public class RunnableImpl implements Runnable{
        //定义一个多个线程共享的票源
        private int ticket = 100;
    
        //设置线程任务:卖票
        @Override
        public void run() {
            //使用死循环,让卖票操作重复执行
            while (true){
                payTicket();
            }
        }
    
        /*
            定义一个同步的方法
            同步方法也会把方法内部的代码锁住
            只让一个线程执行
            同步方法的锁对象是谁?
            就是实现类对象 new RunnableImpl()
            也就是this
         */
        public synchronized void payTicket(){
            //先判断票是否存在
            if(ticket > 0){
                //提高安全问题出现的概率,让程序睡眠
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
    
                //票存在,卖票,ticket--
                System.out.println(Thread.currentThread().getName() + "-->正在卖" + ticket + "张票");
                ticket--;
            }
        }
    }
  • 注:
    • 定义一个同步的方法,同步方法也会把方法内部的代码锁住,只让一个线程执行
    • 同步方法的锁对象是谁?【!】
      • 就是实现类对象 new RunnableImpl(),也就是this
        public /*synchronized*/ void payTicketStatic(){
            synchronized(this){
                //先判断票是否存在
                if(ticket > 0){
                    //提高安全问题出现的概率,让程序睡眠
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
        
        
                    //票存在,卖票,ticket--
                    System.out.println(Thread.currentThread().getName() + "-->正在卖" + ticket + "张票");
                    ticket--;
                }
            }
        }
    
    • 注释掉synchronized修饰符换同步代码块执行(锁对象–>this)

2.8 解决线程安全问题:线程同步—++静态++同步方法(了解)

  • 静态的同步方法锁对象是谁?
    • 不能是this
    • this是创建对象之后产生的【!】,静态方法优先于对象
    • 静态方法锁对象是本类的class属性–>class文件对象(反射)–>今后讲
        
        public static synchronized void payTicketStatic(){
            synchronized(RunnableImpl.class){
                //先判断票是否存在
                if(ticket > 0){
                    //提高安全问题出现的概率,让程序睡眠
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
        
        
                    //票存在,卖票,ticket--
                    System.out.println(Thread.currentThread().getName() + "-->正在卖" + ticket + "张票");
                    ticket--;
                }
            }
        }
    
    • 注:这里的静态代码块中的锁对象就不能是this了
    • 注释掉synchronized修饰符换同步代码块执行(锁对象–>RunnableImpl.class)
    public class RunnableImpl implements Runnable{
        //定义一个多个线程共享的票源
        private static int ticket = 100;
    
        //设置线程任务:卖票
        @Override
        public void run() {
            //使用死循环,让卖票操作重复执行
            while (true){
                payTicketStatic();
            }
        }
    
    
    
        public static synchronized void payTicketStatic(){
            //先判断票是否存在
            if(ticket > 0){
                //提高安全问题出现的概率,让程序睡眠
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
    
                //票存在,卖票,ticket--
                System.out.println(Thread.currentThread().getName() + "-->正在卖" + ticket + "张票");
                ticket--;
            }
        }
    }
  • 注:静态方法访问的变量也要是静态的

2.9 解决线程安全问题:线程同步—Lock锁

  1. 解决线程安全问题的三种方案:使用Lock锁

    1. java.util.concurrent.locks.Lock接口(JDK1.5之后出现)
    2. Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作
  2. Lock接口中的方法:

    • void lock()获取锁。
    • void unlock() 释放锁。
  3. Lock接口的实现类:ReentrantLock

    • java.util.concurrent.locks.ReentrantLock ++implements++ Lock接口
  4. 使用步骤:

    1. 成员位置创建一个ReentrantLock对象
    2. 可能会出现安全问题的代码前,调用Lock接口中的方法lock获取锁
    3. 可能会出现安全问题的代码后,调用Lock接口中的方法unlock释放锁
      • 注:这一步可以优化—>放到finally中【重点】
        • 提高效率
        • ++这样无论程序是否异常,都会把锁释放++
        • 把catch后面的代码放到try中。
    /*
        import java.util.concurrent.locks.Lock;
        import java.util.concurrent.locks.ReentrantLock;
    */
    public class RunnableImpl implements Runnable{
        //定义一个多个线程共享的票源
        private int ticket = 100;
    
        //1. 在==成员位置创建一个ReentrantLock对象==
        Lock l = new ReentrantLock();   //多态
    
        //优化版本将unlock方法放到finally当中——>这样无论程序是否异常,都会把锁释放
        //设置线程任务:卖票
        @Override
        public void run() {
            //使用死循环,让卖票操作重复执行
            while (true){
                //2. 在==可能会出现安全问题的代码前==,调用Lock接口中的==方法lock获取锁==
                l.lock();
    
                //先判断票是否存在
                if(ticket > 0){
                    //提高安全问题出现的概率,让程序睡眠
                    try {
                        Thread.sleep(10);
                        //票存在,卖票,ticket--
                        System.out.println(Thread.currentThread().getName() + "-->正在卖" + ticket + "张票");
                        ticket--;
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }finally {
                        //3. 在==可能会出现安全问题的代码后==,调用Lock接口中的==方法unlock释放锁==
                        l.unlock();
                    }
                }
            }
        }
    
    
        //未优化版本
        /*//设置线程任务:卖票
        @Override
        public void run() {
            //使用死循环,让卖票操作重复执行
            while (true){
                //2. 在==可能会出现安全问题的代码前==,调用Lock接口中的==方法lock获取锁==
                l.lock();
    
                //先判断票是否存在
                if(ticket > 0){
                    //提高安全问题出现的概率,让程序睡眠
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
    
    
                    //票存在,卖票,ticket--
                    System.out.println(Thread.currentThread().getName() + "-->正在卖" + ticket + "张票");
                    ticket--;
                }
    
                //3. 在==可能会出现安全问题的代码后==,调用Lock接口中的==方法unlock释放锁==
                l.unlock();
            }
    
        }*/
    }

第三章 线程状态

3.1 线程状态概述

  1. 在线程的声明周期中有几种状态
  2. java.lang.Thread.Static静态嵌套类)这个枚举中给出了6种状态:
    • NEW(新建状态)
      • 至今尚未启动的线程处于这种状态。
    • RUNNABLE(运行状态)
      • 正在 Java 虚拟机中执行的线程处于这种状态。
    • BLOCKED(阻塞状态)
      • 受阻塞并等待某个监视器锁的线程处于这种状态。
    • WAITING(无限[永久]等待状态)
      • 无限期地等待另一个线程来执行某一特定操作的线程处于这种状态。
    • TIMED_WAITING(休眠[睡眠]状态)
      • 等待另一个线程来执行取决于指定等待时间的操作的线程处于这种状态。
    • TERMINATED(死亡状态)
      • 已退出的线程处于这种状态。
  3. 阻塞和休眠(和无限等待)的区别:
    • 阻塞状态:具有cpu的执行资格,等待cpu空闲时执行
    • 休眠(和无限等待)状态:放弃cpu的执行资格,cpu空闲,也不执行
  4. 无限等待状态和休眠状态区别:
    • 两者合称为:冻结状态
    • 休眠是可以自己醒的,但是无限只能调用Object.notify()方法来醒
    • WAITING
      • Object.wait();–>方法来开启无限等待状态(无参wait)
      • Object.notify();–>方法才能唤醒
        • 注:会继续执行wait方法之后的代码
    • TIMED_WAITING
      • Thread.sleep(long);–>进入睡眠状态的时间
      • Object.wait(long);

3.2 Timed Waiting(计时等待)

3.3 BLOCKED(锁阻塞)

3.4 Waiting(无限等待)

  1. (API):一个正在无限期等待另一个线程执行一个特别的(唤醒)动作的线程处于这一状态。
  2. 等待唤醒案例:线程之间的通信

3.5 等待唤醒案例的实现(3.4案例—Waiting)

  1. 等待唤醒案例:线程之间的通信
    1. 创建一个顾客线程(消费者):告知老板要的包子的种类和数量,调用wait方法,放弃cpu的执行,进入到WAITING状态(无限等待)
      • 注:这里要用锁对象来调用wait方法
    2. 创建一个老板线程(生产者):花了5秒做包子,做好包子之后,调用notify方法,唤醒顾客吃包子
  2. 注意:
    • 顾客和老板线程必须使用同步代码块包裹起来,保证等待和唤醒只能有一个在执行
    • 同步使用的锁对象必须保证唯一
    • 只有锁对象才能调用wait和notify方法
  3. Obejct类中【!】的方法
    • void wait()
      • 在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。
    • void notify()
      • 唤醒在此对象监视器上等待的单个线程。
      • 注:会继续执行wait方法之后的代码
    //主方法:Demo01WaitAndNotify
    public static void main(String[] args) {
        //创建锁对象
        Object obj = new Object();

        //1. ==创建一个顾客线程==(消费者)——这里使用匿名内部类
        new Thread(){
            @Override
            public void run() {
                //顾客的进店次序
                //int i = 1;
                while (true){
                    //保证等待和唤醒只能有一个在执行——要使用同步技术
                    synchronized(obj){
                        //告知老板要的包子的种类和数量
                        System.out.println( "告知老板要的包子的种类和数量");
                        //System.out.println("顾客" + i + "告知老板要的包子的种类和数量");
                        //==调用wait方法==,放弃cpu的执行,进入到WAITING状态(无限等待)
                        try {
                            obj.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        //唤醒之后会继续执行的代码
                        //i++;
                        System.out.println("包子已经做好了,开吃");
                        System.out.println("=====================");
                    }
                }
            }
        }.start();

        //2. ==创建一个老板线程==(生产者)
        new Thread(){
            @Override
            public void run() {
                while (true){
                    //花了5秒做包子
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    //保证等待和唤醒只能有一个在执行——要使用同步技术
                    synchronized(obj){
                        System.out.println("老本5秒钟之后做好包子,告知顾客,可以吃包子了");
                        //做好包子之后,==调用notify方法==,唤醒顾客吃包子
                        obj.notify();
                    }
                }
            }
        }.start();
    }
    
    //结果:
    告知老板要的包子的种类和数量
    老板5秒钟之后做好包子,告知顾客,可以吃包子了
    包子已经做好了,开吃
    =====================
    告知老板要的包子的种类和数量
    老板5秒钟之后做好包子,告知顾客,可以吃包子了
    包子已经做好了,开吃
    =====================
    告知老板要的包子的种类和数量
    ...

3.6 Object类中wait带参方法和notifyAll方法(Timed Waiting)

  1. 进入到TimeWaiting(计时等待)有两种方式
    1. (Thread中)使用sleep(long m)方法,在毫秒值结束之后
      • 线程睡醒进入到Runnable/Blocked状态
    2. (Object中)使用wait(long m)方法,wait方法如果在毫秒值结束之后,还没有被notify唤醒,就会自动醒来【!】
      • 线程睡醒进入到Runnable/Blocked状态
  2. 唤醒的方法:
    • (Object中)void notify() 唤醒在此对象监视器上等待的单个线程
      • 如果有多个等待线程,随机唤醒一个【!】
    • (Object中)void notifyAll() 唤醒在此对象监视器上等待的所有线程
      • 当有多个等待的线程,就不能再使用notify来只唤醒一个,需要用到notifyAll将唤醒所有等待线程【!】
    //主方法:Demo02WaitAndNotify.java
    public static void main(String[] args) {
        //创建锁对象
        Object obj = new Object();

        //1. ==创建一个顾客线程==(消费者)——这里使用匿名内部类
        new Thread(){
            @Override
            public void run() {
                //顾客的进店次序
                //int i = 1;
                while (true){
                    //保证等待和唤醒只能有一个在执行——要使用同步技术
                    synchronized(obj){
                        //告知老板要的包子的种类和数量
                        System.out.println("顾客1告知老板要的包子的种类和数量");
                        //System.out.println("顾客" + i + "告知老板要的包子的种类和数量");
                        //==调用wait方法==,放弃cpu的执行,进入到WAITING状态(无限等待)
                        try {
                            obj.wait(5000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        //唤醒之后会继续执行的代码
                        //i++;
                        System.out.println("包子已经做好了,顾客1开吃");
                    }
                }
            }
        }.start();

        //1. ==创建一个顾客线程==(消费者)——这里使用匿名内部类
        new Thread(){
            @Override
            public void run() {
                //顾客的进店次序
                //int i = 1;
                while (true){
                    //保证等待和唤醒只能有一个在执行——要使用同步技术
                    synchronized(obj){
                        //告知老板要的包子的种类和数量
                        System.out.println("顾客2告知老板要的包子的种类和数量");
                        //System.out.println("顾客" + i + "告知老板要的包子的种类和数量");
                        //==调用wait方法==,放弃cpu的执行,进入到WAITING状态(无限等待)
                        try {
                            obj.wait(5000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        //唤醒之后会继续执行的代码
                        //i++;
                        System.out.println("包子已经做好了,顾客2开吃");
                    }
                }
            }
        }.start();

        //2. ==创建一个老板线程==(生产者)
        new Thread(){
            @Override
            public void run() {
                while (true){
                    //花了5秒做包子
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    //保证等待和唤醒只能有一个在执行——要使用同步技术
                    synchronized(obj){
                        System.out.println("老板5秒钟之后做好包子,告知顾客,可以吃包子了");
                        //做好包子之后,==调用notify方法==,唤醒顾客吃包子
                        //obj.notify(); //如果有多个等待线程,随机唤醒一个
                        obj.notifyAll();    //将唤醒所有等待线程
                    }
                }
            }
        }.start();
    }
    
    //结果:
    顾客1告知老板要的包子的种类和数量
    顾客2告知老板要的包子的种类和数量
    老板5秒钟之后做好包子,告知顾客,可以吃包子了
    包子已经做好了,顾客2开吃
    顾客2告知老板要的包子的种类和数量
    包子已经做好了,顾客1开吃
    顾客1告知老板要的包子的种类和数量
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

狂野小白兔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值