关于后端:Java-的多线程

53次阅读

共计 15905 个字符,预计需要花费 40 分钟才能阅读完成。

1. 实现多线程

1.1 简略理解多线程

是指从软件或者硬件上实现多个线程并发执行的技术。
具备多线程能力的计算机因有硬件反对而可能在同一时间执行多个线程,晋升性能。

1.2 并发和并行

  • 并行:在同一时刻,有多个指令在多个 CPU 上同时执行。
  • 并发:在同一时刻,有多个指令在单个 CPU 上交替执行。

1.3 过程和线程

  • 过程:是正在运行的程序

    独立性:过程是一个能独立运行的根本单位,同时也是零碎分配资源和调度的独立单位
    动态性:过程的本质是程序的一次执行过程,过程是动静产生,动静沦亡的
    并发性:任何过程都能够同其余过程一起并发执行

  • 线程:是过程中的单个顺序控制流,是一条执行门路

    ​ 单线程:一个过程如果只有一条执行门路,则称为单线程程序

    ​ 多线程:一个过程如果有多条执行门路,则称为多线程程序

1.4 实现多线程形式一:继承 Thread 类

  • 办法介绍

    办法名 阐明
    void run() 在线程开启后,此办法将被调用执行
    void start() 使此线程开始执行,Java 虚构机会调用 run 办法()
  • 实现步骤

    • 定义一个类 MyThread 继承 Thread 类
    • 在 MyThread 类中重写 run()办法
    • 创立 MyThread 类的对象
    • 启动线程
  • 代码演示

    public class MyThread extends Thread {
        @Override
        public void run() {for(int i=0; i<100; i++) {System.out.println(i);
            }
        }
    }
    public class MyThreadDemo {public static void main(String[] args) {MyThread my1 = new MyThread();
            MyThread my2 = new MyThread();
    
    //        my1.run();
    //        my2.run();
    
            //void start() 导致此线程开始执行; Java 虚拟机调用此线程的 run 办法
            my1.start();
            my2.start();}
    }
  • 两个小问题

    • 为什么要重写 run()办法?

      因为 run()是用来封装被线程执行的代码

    • run()办法和 start()办法的区别?

      run():封装线程执行的代码,间接调用,相当于一般办法的调用

      start():启动线程;而后由 JVM 调用此线程的 run()办法

1.5 实现多线程形式二:实现 Runnable 接口

  • Thread 构造方法

    办法名 阐明
    Thread(Runnable target) 调配一个新的 Thread 对象
    Thread(Runnable target, String name) 调配一个新的 Thread 对象
  • 实现步骤

    • 定义一个类 MyRunnable 实现 Runnable 接口
    • 在 MyRunnable 类中重写 run()办法
    • 创立 MyRunnable 类的对象
    • 创立 Thread 类的对象,把 MyRunnable 对象作为构造方法的参数
    • 启动线程
  • 代码演示

    public class MyRunnable implements Runnable {
        @Override
        public void run() {for(int i=0; i<100; i++) {System.out.println(Thread.currentThread().getName()+":"+i);
            }
        }
    }
    public class MyRunnableDemo {public static void main(String[] args) {
            // 创立 MyRunnable 类的对象
            MyRunnable my = new MyRunnable();
    
            // 创立 Thread 类的对象,把 MyRunnable 对象作为构造方法的参数
            //Thread(Runnable target)
    //        Thread t1 = new Thread(my);
    //        Thread t2 = new Thread(my);
            //Thread(Runnable target, String name)
            Thread t1 = new Thread(my,"坦克");
            Thread t2 = new Thread(my,"飞机");
    
            // 启动线程
            t1.start();
            t2.start();}
    }

1.6 实现多线程形式三: 实现 Callable 接口

  • 办法介绍

    办法名 阐明
    V call() 计算结果,如果无奈计算结果,则抛出一个异样
    FutureTask(Callable<V> callable) 创立一个 FutureTask,一旦运行就执行给定的 Callable
    V get() 如有必要,期待计算实现,而后获取其后果
  • 实现步骤

    • 定义一个类 MyCallable 实现 Callable 接口
    • 在 MyCallable 类中重写 call()办法
    • 创立 MyCallable 类的对象
    • 创立 Future 的实现类 FutureTask 对象,把 MyCallable 对象作为构造方法的参数
    • 创立 Thread 类的对象,把 FutureTask 对象作为构造方法的参数
    • 启动线程
    • 再调用 get 办法,就能够获取线程完结之后的后果。
  • 代码演示

    public class MyCallable implements Callable<String> {
        @Override
        public String call() throws Exception {for (int i = 0; i < 100; i++) {System.out.println("跟女孩表白" + i);
            }
            // 返回值就示意线程运行结束之后的后果
            return "许可";
        }
    }
    public class Demo {public static void main(String[] args) throws ExecutionException, InterruptedException {
            // 线程开启之后须要执行外面的 call 办法
            MyCallable mc = new MyCallable();
    
            //Thread t1 = new Thread(mc);
    
            // 能够获取线程执行结束之后的后果. 也能够作为参数传递给 Thread 对象
            FutureTask<String> ft = new FutureTask<>(mc);
    
            // 创立线程对象
            Thread t1 = new Thread(ft);
    
            String s = ft.get();
            // 开启线程
            t1.start();
    
            //String s = ft.get();
            System.out.println(s);
        }
    }
  • 三种实现形式的比照

    • 实现 Runnable、Callable 接口

      • 益处: 扩展性强,实现该接口的同时还能够继承其余的类
      • 毛病: 编程绝对简单,不能间接应用 Thread 类中的办法
    • 继承 Thread 类

      • 益处: 编程比较简单,能够间接应用 Thread 类中的办法
      • 毛病: 能够扩展性较差,不能再继承其余的类

1.7 设置和获取线程名称

  • 办法介绍

    办法名 阐明
    void setName(String name) 将此线程的名称更改为等于参数 name
    String getName() 返回此线程的名称
    Thread currentThread() 返回对以后正在执行的线程对象的援用
  • 代码演示

    public class MyThread extends Thread {public MyThread() {}
        public MyThread(String name) {super(name);
        }
    
        @Override
        public void run() {for (int i = 0; i < 100; i++) {System.out.println(getName()+":"+i);
            }
        }
    }
    public class MyThreadDemo {public static void main(String[] args) {MyThread my1 = new MyThread();
            MyThread my2 = new MyThread();
    
            //void setName(String name):将此线程的名称更改为等于参数 name
            my1.setName("高铁");
            my2.setName("飞机");
    
            //Thread(String name)
            MyThread my1 = new MyThread("高铁");
            MyThread my2 = new MyThread("飞机");
    
            my1.start();
            my2.start();
    
            //static Thread currentThread() 返回对以后正在执行的线程对象的援用
            System.out.println(Thread.currentThread().getName());
        }
    }

1.8 线程休眠

  • 相干办法

    办法名 阐明
    static void sleep(long millis) 使以后正在执行的线程停留(暂停执行)指定的毫秒数
  • 代码演示

    public class MyRunnable implements Runnable {
        @Override
        public void run() {for (int i = 0; i < 100; i++) {
                try {Thread.sleep(100);
                } catch (InterruptedException e) {e.printStackTrace();
                }
    
                System.out.println(Thread.currentThread().getName() + "---" + i);
            }
        }
    }
    public class Demo {public static void main(String[] args) throws InterruptedException {/*System.out.println("睡觉前");
            Thread.sleep(3000);
            System.out.println("睡醒了");*/
    
            MyRunnable mr = new MyRunnable();
    
            Thread t1 = new Thread(mr);
            Thread t2 = new Thread(mr);
    
            t1.start();
            t2.start();}
    }

1.9 线程优先级

  • 线程调度

    • 两种调度形式

      • 分时调度模型:所有线程轮流应用 CPU 的使用权,平均分配每个线程占用 CPU 的工夫片
      • 抢占式调度模型:优先让优先级高的线程应用 CPU,如果线程的优先级雷同,那么会随机抉择一个,优先级高的线程获取的 CPU 工夫片绝对多一些
    • Java 应用的是抢占式调度模型
    • 随机性

      如果计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,线程只有失去 CPU 工夫片,也就是使用权,才能够执行指令。所以说多线程程序的执行是有随机性,因为谁抢到 CPU 的使用权是不肯定的

  • 优先级相干办法

    办法名 阐明
    final int getPriority() 返回此线程的优先级
    final void setPriority(int newPriority) 更改此线程的优先级线程默认优先级是 5;线程优先级的范畴是:1-10
  • 代码演示

    public class MyCallable implements Callable<String> {
        @Override
        public String call() throws Exception {for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName() + "---" + i);
            }
            return "线程执行结束了";
        }
    }
    public class Demo {public static void main(String[] args) {
            // 优先级: 1 - 10 默认值:5
            MyCallable mc = new MyCallable();
    
            FutureTask<String> ft = new FutureTask<>(mc);
    
            Thread t1 = new Thread(ft);
            t1.setName("飞机");
            t1.setPriority(10);
            //System.out.println(t1.getPriority());//5
            t1.start();
    
            MyCallable mc2 = new MyCallable();
    
            FutureTask<String> ft2 = new FutureTask<>(mc2);
    
            Thread t2 = new Thread(ft2);
            t2.setName("坦克");
            t2.setPriority(1);
            //System.out.println(t2.getPriority());//5
            t2.start();}
    }

1.10 守护线程

  • 相干办法

    办法名 阐明
    void setDaemon(boolean on) 将此线程标记为守护线程,当运行的线程都是守护线程时,Java 虚拟机将退出
  • 代码演示

    public class MyThread1 extends Thread {
        @Override
        public void run() {for (int i = 0; i < 10; i++) {System.out.println(getName() + "---" + i);
            }
        }
    }
    public class MyThread2 extends Thread {
        @Override
        public void run() {for (int i = 0; i < 100; i++) {System.out.println(getName() + "---" + i);
            }
        }
    }
    public class Demo {public static void main(String[] args) {MyThread1 t1 = new MyThread1();
            MyThread2 t2 = new MyThread2();
    
            t1.setName("女神");
            t2.setName("备胎");
    
            // 把第二个线程设置为守护线程
            // 当一般线程执行完之后, 那么守护线程也没有持续运行上来的必要了.
            t2.setDaemon(true);
    
            t1.start();
            t2.start();}
    }

2. 线程同步

2.1 卖票

  • 案例需要

    某电影院目前正在上映国产大片,共有 100 张票,而它有 3 个窗口卖票,请设计一个程序模仿该电影院卖票

  • 实现步骤

    • 定义一个类 SellTicket 实现 Runnable 接口,外面定义一个成员变量:private int tickets = 100;
    • 在 SellTicket 类中重写 run()办法实现卖票,代码步骤如下
    • 判断票数大于 0,就卖票,并告知是哪个窗口卖的
    • 卖了票之后,总票数要减 1
    • 票卖没了,线程进行
    • 定义一个测试类 SellTicketDemo,外面有 main 办法,代码步骤如下
    • 创立 SellTicket 类的对象
    • 创立三个 Thread 类的对象,把 SellTicket 对象作为构造方法的参数,并给出对应的窗口名称
    • 启动线程
  • 代码实现

    public class SellTicket implements Runnable {
        private int tickets = 100;
        // 在 SellTicket 类中重写 run()办法实现卖票,代码步骤如下
        @Override
        public void run() {while (true) {if(ticket <= 0){
                        // 卖完了
                        break;
                    }else{
                        try {Thread.sleep(100);
                        } catch (InterruptedException e) {e.printStackTrace();
                        }
                        ticket--;
                        System.out.println(Thread.currentThread().getName() + "在卖票, 还剩下" + ticket + "张票");
                    }
            }
        }
    }
    public class SellTicketDemo {public static void main(String[] args) {
            // 创立 SellTicket 类的对象
            SellTicket st = new SellTicket();
    
            // 创立三个 Thread 类的对象,把 SellTicket 对象作为构造方法的参数,并给出对应的窗口名称
            Thread t1 = new Thread(st,"窗口 1");
            Thread t2 = new Thread(st,"窗口 2");
            Thread t3 = new Thread(st,"窗口 3");
    
            // 启动线程
            t1.start();
            t2.start();
            t3.start();}
    }

2.2 卖票案例的问题

  • 卖票呈现了问题

    • 雷同的票呈现了屡次
    • 呈现了正数的票
  • 问题产生起因

    线程执行的随机性导致的, 可能在卖票过程中失落 cpu 的执行权, 导致呈现问题

2.3 同步代码块解决数据安全问题

  • 平安问题呈现的条件

    • 是多线程环境
    • 有共享数据
    • 有多条语句操作共享数据
  • 如何解决多线程平安问题呢?

    • 根本思维:让程序没有平安问题的环境
  • 怎么实现呢?

    • 把多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可
    • Java 提供了同步代码块的形式来解决
  • 同步代码块格局:

    synchronized(任意对象) {多条语句操作共享数据的代码}

    synchronized(任意对象):就相当于给代码加锁了,任意对象就能够看成是一把锁

  • 同步的益处和弊病

    • 益处:解决了多线程的数据安全问题
    • 弊病:当线程很多时,因为每个线程都会去判断同步上的锁,这是很消耗资源的,无形中会升高程序的运行效率
  • 代码演示

    public class SellTicket implements Runnable {
        private int tickets = 100;
        private Object obj = new Object();
    
        @Override
        public void run() {while (true) {synchronized (obj) { // 对可能有平安问题的代码加锁, 多个线程必须应用同一把锁
                    //t1 进来后,就会把这段代码给锁起来
                    if (tickets > 0) {
                        try {Thread.sleep(100);
                            //t1 劳动 100 毫秒
                        } catch (InterruptedException e) {e.printStackTrace();
                        }
                        // 窗口 1 正在发售第 100 张票
                        System.out.println(Thread.currentThread().getName() + "正在发售第" + tickets + "张票");
                        tickets--; //tickets = 99;
                    }
                }
                //t1 进去了,这段代码的锁就被开释了
            }
        }
    }
    
    public class SellTicketDemo {public static void main(String[] args) {SellTicket st = new SellTicket();
    
            Thread t1 = new Thread(st, "窗口 1");
            Thread t2 = new Thread(st, "窗口 2");
            Thread t3 = new Thread(st, "窗口 3");
    
            t1.start();
            t2.start();
            t3.start();}
    }

2.4 同步办法解决数据安全问题

  • 同步办法的格局

    同步办法:就是把 synchronized 关键字加到办法上

    修饰符 synchronized 返回值类型 办法名(办法参数) {办法体;}

    同步办法的锁对象是什么呢?

    ​ this

  • 动态同步办法

    同步静态方法:就是把 synchronized 关键字加到静态方法上

    修饰符 static synchronized 返回值类型 办法名(办法参数) {办法体;}

    同步静态方法的锁对象是什么呢?

    ​ 类名.class

  • 代码演示

    public class MyRunnable implements Runnable {
        private static int ticketCount = 100;
    
        @Override
        public void run() {while(true){if("窗口一".equals(Thread.currentThread().getName())){
                    // 同步办法
                    boolean result = synchronizedMthod();
                    if(result){break;}
                }
    
                if("窗口二".equals(Thread.currentThread().getName())){
                    // 同步代码块
                    synchronized (MyRunnable.class){if(ticketCount == 0){break;}else{
                            try {Thread.sleep(10);
                            } catch (InterruptedException e) {e.printStackTrace();
                            }
                            ticketCount--;
                            System.out.println(Thread.currentThread().getName() + "在卖票, 还剩下" + ticketCount + "张票");
                        }
                    }
                }
    
            }
        }
    
        private static synchronized boolean synchronizedMthod() {if(ticketCount == 0){return true;}else{
                try {Thread.sleep(10);
                } catch (InterruptedException e) {e.printStackTrace();
                }
                ticketCount--;
                System.out.println(Thread.currentThread().getName() + "在卖票, 还剩下" + ticketCount + "张票");
                return false;
            }
        }
    }
    
    public class Demo {public static void main(String[] args) {MyRunnable mr = new MyRunnable();
    
            Thread t1 = new Thread(mr);
            Thread t2 = new Thread(mr);
      
            t1.setName("窗口一");
            t2.setName("窗口二");
      
            t1.start();
            t2.start();}
    }

2.5Lock 锁

尽管咱们能够了解同步代码块和同步办法的锁对象问题,然而咱们并没有间接看到在哪里加上了锁,在哪里开释了锁,为了更清晰的表白如何加锁和开释锁,JDK5 当前提供了一个新的锁对象 Lock

Lock 是接口不能间接实例化,这里采纳它的实现类 ReentrantLock 来实例化

  • ReentrantLock 构造方法

    办法名 阐明
    ReentrantLock() 创立一个 ReentrantLock 的实例
  • 加锁解锁办法

    办法名 阐明
    void lock() 取得锁
    void unlock() 开释锁
  • 代码演示

    public class Ticket implements Runnable {
        // 票的数量
        private int ticket = 100;
        private Object obj = new Object();
        private ReentrantLock lock = new ReentrantLock();
    
        @Override
        public void run() {while (true) {//synchronized (obj){// 多个线程必须应用同一把锁.
                try {lock.lock();
                    if (ticket <= 0) {
                        // 卖完了
                        break;
                    } else {Thread.sleep(100);
                        ticket--;
                        System.out.println(Thread.currentThread().getName() + "在卖票, 还剩下" + ticket + "张票");
                    }
                } catch (InterruptedException e) {e.printStackTrace();
                } finally {lock.unlock();
                }
                // }
            }
        }
    }
    
    public class Demo {public static void main(String[] args) {Ticket ticket = new Ticket();
    
            Thread t1 = new Thread(ticket);
            Thread t2 = new Thread(ticket);
            Thread t3 = new Thread(ticket);
    
            t1.setName("窗口一");
            t2.setName("窗口二");
            t3.setName("窗口三");
    
            t1.start();
            t2.start();
            t3.start();}
    }

2.6 死锁

  • 概述

    线程死锁是指因为两个或者多个线程相互持有对方所须要的资源,导致这些线程处于期待状态,无奈返回执行

  • 什么状况下会产生死锁

    1. 资源无限
    2. 同步嵌套
  • 代码演示

    public class Demo {public static void main(String[] args) {Object objA = new Object();
            Object objB = new Object();
    
            new Thread(()->{while(true){synchronized (objA){
                        // 线程一
                        synchronized (objB){System.out.println("小康同学正在走路");
                        }
                    }
                }
            }).start();
    
            new Thread(()->{while(true){synchronized (objB){
                        // 线程二
                        synchronized (objA){System.out.println("小薇同学正在走路");
                        }
                    }
                }
            }).start();}
    }

3. 生产者消费者

3.1 生产者和消费者模式概述

  • 概述

    生产者消费者模式是一个非常经典的多线程合作的模式,弄懂生产者消费者问题可能让咱们对多线程编程的了解更加粗浅。

    所谓生产者消费者问题,实际上次要是蕴含了两类线程:

    ​ 一类是生产者线程用于生产数据

    ​ 一类是消费者线程用于生产数据

    为理解耦生产者和消费者的关系,通常会采纳共享的数据区域,就像是一个仓库

    生产者生产数据之后间接搁置在共享数据区中,并不需要关怀消费者的行为

    消费者只须要从共享数据区中去获取数据,并不需要关怀生产者的行为

  • Object 类的期待和唤醒办法

    办法名 阐明
    void wait() 导致以后线程期待,直到另一个线程调用该对象的 notify()办法或 notifyAll()办法
    void notify() 唤醒正在期待对象监视器的单个线程
    void notifyAll() 唤醒正在期待对象监视器的所有线程

3.2 生产者和消费者案例

  • 案例需要

    • 桌子类(Desk):定义示意包子数量的变量, 定义锁对象变量, 定义标记桌子上有无包子的变量
    • 生产者类 (Cooker):实现 Runnable 接口,重写 run() 办法,设置线程工作

      1. 判断是否有包子, 决定以后线程是否执行

      2. 如果有包子, 就进入期待状态, 如果没有包子, 继续执行, 生产包子

      3. 生产包子之后, 更新桌子上包子状态, 唤醒消费者生产包子

    • 消费者类 (Foodie):实现 Runnable 接口,重写 run() 办法,设置线程工作

      1. 判断是否有包子, 决定以后线程是否执行

      2. 如果没有包子, 就进入期待状态, 如果有包子, 就生产包子

      3. 生产包子后, 更新桌子上包子状态, 唤醒生产者生产包子

    • 测试类(Demo):外面有 main 办法,main 办法中的代码步骤如下

      创立生产者线程和消费者线程对象

      别离开启两个线程

  • 代码实现

    public class Desk {
    
        // 定义一个标记
        //true 就示意桌子上有汉堡包的, 此时容许吃货执行
        //false 就示意桌子上没有汉堡包的, 此时容许厨师执行
        public static boolean flag = false;
    
        // 汉堡包的总数量
        public static int count = 10;
    
        // 锁对象
        public static final Object lock = new Object();}
    
    public class Cooker extends Thread {
    //    生产者步骤://            1,判断桌子上是否有汉堡包
    //    如果有就期待,如果没有才生产。//            2,把汉堡包放在桌子上。//            3,叫醒期待的消费者开吃。@Override
        public void run() {while(true){synchronized (Desk.lock){if(Desk.count == 0){break;}else{if(!Desk.flag){
                            // 生产
                            System.out.println("厨师正在生产汉堡包");
                            Desk.flag = true;
                            Desk.lock.notifyAll();}else{
                            try {Desk.lock.wait();
                            } catch (InterruptedException e) {e.printStackTrace();
                            }
                        }
                    }
                }
            }
        }
    }
    
    public class Foodie extends Thread {
        @Override
        public void run() {
    //        1,判断桌子上是否有汉堡包。//        2,如果没有就期待。//        3,如果有就开吃
    //        4,吃完之后,桌子上的汉堡包就没有了
    //                叫醒期待的生产者持续生产
    //        汉堡包的总数量减一
    
            // 套路:
                //1. while(true)死循环
                //2. synchronized 锁, 锁对象要惟一
                //3. 判断, 共享数据是否完结. 完结
                //4. 判断, 共享数据是否完结. 没有完结
            while(true){synchronized (Desk.lock){if(Desk.count == 0){break;}else{if(Desk.flag){
                            // 有
                            System.out.println("吃货在吃汉堡包");
                            Desk.flag = false;
                            Desk.lock.notifyAll();
                            Desk.count--;
                        }else{
                            // 没有就期待
                            // 应用什么对象当做锁, 那么就必须用这个对象去调用期待和唤醒的办法.
                            try {Desk.lock.wait();
                            } catch (InterruptedException e) {e.printStackTrace();
                            }
                        }
                    }
                }
            }
    
        }
    }
    
    public class Demo {public static void main(String[] args) {
            /* 消费者步骤:1,判断桌子上是否有汉堡包。2,如果没有就期待。3,如果有就开吃
            4,吃完之后,桌子上的汉堡包就没有了
                    叫醒期待的生产者持续生产
            汉堡包的总数量减一 */
    
            /* 生产者步骤:1,判断桌子上是否有汉堡包
            如果有就期待,如果没有才生产。2,把汉堡包放在桌子上。3,叫醒期待的消费者开吃。*/
    
            Foodie f = new Foodie();
            Cooker c = new Cooker();
    
            f.start();
            c.start();}
    }

3.3 生产者和消费者案例优化

  • 需要

    • 将 Desk 类中的变量, 采纳面向对象的形式封装起来
    • 生产者和消费者类中构造方法接管 Desk 类对象, 之后在 run 办法中进行应用
    • 创立生产者和消费者线程对象, 构造方法中传入 Desk 类对象
    • 开启两个线程
  • 代码实现

    public class Desk {
    
        // 定义一个标记
        //true 就示意桌子上有汉堡包的, 此时容许吃货执行
        //false 就示意桌子上没有汉堡包的, 此时容许厨师执行
        //public static boolean flag = false;
        private boolean flag;
    
        // 汉堡包的总数量
        //public static int count = 10;
        // 当前咱们在应用这种必须有默认值的变量
       // private int count = 10;
        private int count;
    
        // 锁对象
        //public static final Object lock = new Object();
        private final Object lock = new Object();
    
        public Desk() {this(false,10); // 在空参外部调用带参, 对成员变量进行赋值, 之后就能够间接应用成员变量了
        }
    
        public Desk(boolean flag, int count) {
            this.flag = flag;
            this.count = count;
        }
    
        public boolean isFlag() {return flag;}
    
        public void setFlag(boolean flag) {this.flag = flag;}
    
        public int getCount() {return count;}
    
        public void setCount(int count) {this.count = count;}
    
        public Object getLock() {return lock;}
    
        @Override
        public String toString() {
            return "Desk{" +
                    "flag=" + flag +
                    ", count=" + count +
                    ", lock=" + lock +
                    '}';
        }
    }
    
    public class Cooker extends Thread {
    
        private Desk desk;
    
        public Cooker(Desk desk) {this.desk = desk;}
    //    生产者步骤://            1,判断桌子上是否有汉堡包
    //    如果有就期待,如果没有才生产。//            2,把汉堡包放在桌子上。//            3,叫醒期待的消费者开吃。@Override
        public void run() {while(true){synchronized (desk.getLock()){if(desk.getCount() == 0){break;}else{//System.out.println("验证一下是否执行了");
                        if(!desk.isFlag()){
                            // 生产
                            System.out.println("厨师正在生产汉堡包");
                            desk.setFlag(true);
                            desk.getLock().notifyAll();
                        }else{
                            try {desk.getLock().wait();} catch (InterruptedException e) {e.printStackTrace();
                            }
                        }
                    }
                }
            }
        }
    }
    
    public class Foodie extends Thread {
        private Desk desk;
    
        public Foodie(Desk desk) {this.desk = desk;}
    
        @Override
        public void run() {
    //        1,判断桌子上是否有汉堡包。//        2,如果没有就期待。//        3,如果有就开吃
    //        4,吃完之后,桌子上的汉堡包就没有了
    //                叫醒期待的生产者持续生产
    //        汉堡包的总数量减一
    
            // 套路:
                //1. while(true)死循环
                //2. synchronized 锁, 锁对象要惟一
                //3. 判断, 共享数据是否完结. 完结
                //4. 判断, 共享数据是否完结. 没有完结
            while(true){synchronized (desk.getLock()){if(desk.getCount() == 0){break;}else{//System.out.println("验证一下是否执行了");
                        if(desk.isFlag()){
                            // 有
                            System.out.println("吃货在吃汉堡包");
                            desk.setFlag(false);
                            desk.getLock().notifyAll();
                            desk.setCount(desk.getCount() - 1);
                        }else{
                            // 没有就期待
                            // 应用什么对象当做锁, 那么就必须用这个对象去调用期待和唤醒的办法.
                            try {desk.getLock().wait();} catch (InterruptedException e) {e.printStackTrace();
                            }
                        }
                    }
                }
            }
    
        }
    }
    
    public class Demo {public static void main(String[] args) {
            /* 消费者步骤:1,判断桌子上是否有汉堡包。2,如果没有就期待。3,如果有就开吃
            4,吃完之后,桌子上的汉堡包就没有了
                    叫醒期待的生产者持续生产
            汉堡包的总数量减一 */
    
            /* 生产者步骤:1,判断桌子上是否有汉堡包
            如果有就期待,如果没有才生产。2,把汉堡包放在桌子上。3,叫醒期待的消费者开吃。*/
    
            Desk desk = new Desk();
    
            Foodie f = new Foodie(desk);
            Cooker c = new Cooker(desk);
    
            f.start();
            c.start();}
    }

3.4 阻塞队列根本应用

  • 阻塞队列继承构造
  • 常见 BlockingQueue:

    ArrayBlockingQueue: 底层是数组, 有界

    LinkedBlockingQueue: 底层是链表, 无界. 但不是真正的无界, 最大为 int 的最大值

  • BlockingQueue 的外围办法:

    put(anObject): 将参数放入队列, 如果放不进去会阻塞

    take(): 取出第一个数据, 取不到会阻塞

  • 代码示例

    public class Demo02 {public static void main(String[] args) throws Exception {
            // 创立阻塞队列的对象, 容量为 1
            ArrayBlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue<>(1);
    
            // 存储元素
            arrayBlockingQueue.put("汉堡包");
    
            // 取元素
            System.out.println(arrayBlockingQueue.take());
            System.out.println(arrayBlockingQueue.take()); // 取不到会阻塞
    
            System.out.println("程序完结了");
        }
    }

3.5 阻塞队列实现期待唤醒机制

  • 案例需要

    • 生产者类 (Cooker):实现 Runnable 接口,重写 run() 办法,设置线程工作

      1. 构造方法中接管一个阻塞队列对象

      2. 在 run 办法中循环向阻塞队列中增加包子

      3. 打印增加后果

    • 消费者类 (Foodie):实现 Runnable 接口,重写 run() 办法,设置线程工作

      1. 构造方法中接管一个阻塞队列对象

      2. 在 run 办法中循环获取阻塞队列中的包子

      3. 打印获取后果

    • 测试类(Demo):外面有 main 办法,main 办法中的代码步骤如下

      创立阻塞队列对象

      创立生产者线程和消费者线程对象, 构造方法中传入阻塞队列对象

      别离开启两个线程

  • 代码实现

    public class Cooker extends Thread {
    
        private ArrayBlockingQueue<String> bd;
    
        public Cooker(ArrayBlockingQueue<String> bd) {this.bd = bd;}
    //    生产者步骤://            1,判断桌子上是否有汉堡包
    //    如果有就期待,如果没有才生产。//            2,把汉堡包放在桌子上。//            3,叫醒期待的消费者开吃。@Override
        public void run() {while (true) {
                try {bd.put("汉堡包");
                    System.out.println("厨师放入一个汉堡包");
                } catch (InterruptedException e) {e.printStackTrace();
                }
            }
        }
    }
    
    public class Foodie extends Thread {
        private ArrayBlockingQueue<String> bd;
    
        public Foodie(ArrayBlockingQueue<String> bd) {this.bd = bd;}
    
        @Override
        public void run() {
    //        1,判断桌子上是否有汉堡包。//        2,如果没有就期待。//        3,如果有就开吃
    //        4,吃完之后,桌子上的汉堡包就没有了
    //                叫醒期待的生产者持续生产
    //        汉堡包的总数量减一
    
            // 套路:
            //1. while(true)死循环
            //2. synchronized 锁, 锁对象要惟一
            //3. 判断, 共享数据是否完结. 完结
            //4. 判断, 共享数据是否完结. 没有完结
            while (true) {
                try {String take = bd.take();
                    System.out.println("吃货将" + take + "拿进去吃了");
                } catch (InterruptedException e) {e.printStackTrace();
                }
            }
    
        }
    }
    
    public class Demo {public static void main(String[] args) {ArrayBlockingQueue<String> bd = new ArrayBlockingQueue<>(1);
    
            Foodie f = new Foodie(bd);
            Cooker c = new Cooker(bd);
    
            f.start();
            c.start();}
    }

正文完
 0