zoukankan      html  css  js  c++  java
  • Java进阶: 多线程,线程类三种方式,线程调度,线程同步,死锁,线程间的通信,阻塞队列,wait和sleep区别?

                          重难点梳理

                          知识点梳理

                              学习目标

    1、能够知道什么是进程什么是线程(进程和线程的概述,多进程和多线程的意义)

    2、能够掌握线程常见API的使用

    3、能够理解什么是线程安全问题

    4、能够知道什么是锁

    5、能够知道什么是死锁

    6、能够掌握线程3种创建方式(3种创建方式)

    7、能够知道什么是等待唤醒机制

     

                              超详细讲义

    ==知识点==

    1. 多线程的概念

    2. 多线程的实现方式

    3. 线程类的常见方法

    4. 线程同步

    5. 死锁

    6. 生产者消费者

    1.多线程的概念

    1.1初步了解多线程【重点】

    1.什么是多线程?

    采用多线程技术可以同时执行多个任务(比如:迅雷同时下载多个任务),多线程需要硬件支持

    1.2并发和并行【重点】

    1.什么是并行?

    在同一时刻,有多条线程在多个CPU上同时执行

    2.什么是并发?

    在一段时间内,有多条线程在单个CPU上交替执行

    1.3进程和线程【重点】

    (共4点)

    1.什么是进程?

    可以理解为正在运行的程序

    2.什么是线程?

    线程它是进程的一部分,是进程中的单个控制流,是一条执行路径(执行一项任务)

    一个进程,可以有多条线程,至少有一条线程(线程才是CPU执行的最小单元)

    线程只能在进程中运行

    ==3.为什么要学习多线程==

    可以让程序同时执行多个任务,提高程序的执行效率(例如一次上传多张图片,同时打开多个网页)

    2.多线程的实现方式

    2.1实现多线程方式一:继承Thread类【重点】

    1.实现多线程的方式有哪些?

    • 继承Thread类的方式进行实现

    • 实现Runnable接口的方式进行实现

    • 利用Callable和Future接口方式实现

    2.方法介绍

    方法名说明
    void run() 在线程开启后,此方法将被调用执行
    void start() 使此线程开始执行,Java虚拟机会调用run方法()

    3.实现步骤

    • 定义一个类MyThread继承Thread类

    • 在MyThread类中重写run()方法

    • 创建MyThread类的对象

    • 启动线程

    代码演示

    package com.itheima.threaddemo1;

    public class MyThread extends Thread{
       @Override
       public void run() {
           //代码就是线程在开启之后执行的代码
           for (int i = 0; i < 100; i++) {
               System.out.println("线程开启了" + i);
          }
      }
    }

    package com.itheima.threaddemo1;

    public class Demo {
       public static void main(String[] args) {
           //创建一个线程对象
           MyThread t1 = new MyThread();
           //创建一个线程对象
           MyThread t2 = new MyThread();

           //t1.run();//表示的仅仅是创建对象,用对象去调用方法,并没有开启线程.
           //t2.run();
           //开启一条线程
           t1.start();
           //开启第二条线程
           t2.start();
      }
    }

    2.2 多线程的实现方式-两个小问题【了解】

    1.为什么要重写run()方法?

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

    2.run()方法和start()方法的区别?

    run():封装线程执行的代码,直接调用,相当于普通方法的调用

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

    2.3实现多线程方式二:实现Runnable接口【重点】

    (共3点)

    1.Thread构造方法

    方法名说明
    Thread(Runnable target) 分配一个新的Thread对象

    2.实现步骤

    • 定义一个类MyRunnable实现Runnable接口

    • 在MyRunnable类中重写run()方法

    • 创建MyRunnable类的对象

    • 创建Thread类的对象,把MyRunnable对象作为构造方法的参数

    • 启动线程

    代码演示

    package com.itheima.threaddemo2;

    public class MyRunnable implements Runnable{
       @Override
       public void run() {
           //线程启动后执行的代码
           for (int i = 0; i < 100; i++) {
               System.out.println(Thread.currentThread().getName() + "第二种方式实现多线程" + i);
          }
      }
    }

    package com.itheima.threaddemo2;

    public class Demo {
       public static void main(String[] args) {
           //创建了一个参数的对象
           MyRunnable mr = new MyRunnable();
           //创建了一个线程对象,并把参数传递给这个线程.
           //在线程启动之后,执行的就是参数里面的run方法
           Thread t1 = new Thread(mr);
           //开启线程
           t1.start();


           MyRunnable mr2 = new MyRunnable();
           Thread t2 = new Thread(mr2);
           t2.start();

      }
    }

    ==3.线程的执行是顺序是随机的==

    2.4 实现多线程方式三: 实现Callable接口【重点】

    1.方法介绍

    方法名说明
    V call() 计算结果,如果无法计算结果,则抛出一个异常
    FutureTask(Callable<V> callable) 创建一个 FutureTask,一旦运行就执行给定的 Callable
    V get() 如有必要,等待计算完成,然后获取其结果

    2.实现步骤

    • 定义一个类MyCallable实现Callable接口

    • 在MyCallable类中重写call()方法

    • 创建MyCallable类的对象

    • 创建Future的实现类FutureTask对象,把MyCallable对象作为构造方法的参数

    • 创建Thread类的对象,把FutureTask对象作为构造方法的参数

    • 启动线程

    • 再调用get方法,就可以获取线程结束之后的结果。

    3.注意事项

    get()方法的调用一定要在Thread类对象调用start()方法之后

    代码演示

    package com.itheima.threaddemo3;

    import java.util.concurrent.Callable;

    public class MyCallable implements Callable<String> {
       @Override
       public String call() throws Exception {
           for (int i = 0; i < 100; i++) {
               System.out.println("跟女孩表白" + i);
          }
           //返回值就表示线程运行完毕之后的结果
           return "答应";
      }
    }


    package com.itheima.threaddemo3;

    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.FutureTask;

    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);
      }
    }

    2.5 三种实现方式的对比【重点】

    1.实现Runnable、Callable接口

    • 好处: 扩展性强,实现该接口的同时还可以继承其他的类

    • 缺点: 编程相对复杂,不能直接使用Thread类中的方法

    2.继承Thread类

    • 好处: 编程比较简单,可以直接使用Thread类中的方法

    • 缺点: 可以扩展性较差,不能再继承其他的类

    3.线程类中的常见方法

    3.1设置和获取线程名称【重点】

    方法介绍

    方法名说明
    void setName(String name) 将此线程的名称更改为等于参数name
    String getName() 返回此线程的名称

    代码演示

    package com.itheima.threaddemo4;

    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);
          }
      }
    }

    package com.itheima.threaddemo4;

    public class Demo {
       //1,线程是有默认名字的,格式:Thread-编号
       public static void main(String[] args) {
           MyThread t1 = new MyThread("小蔡");
           MyThread t2 = new MyThread("小强");

           //t1.setName("小蔡");
           //t2.setName("小强");

           t1.start();
           t2.start();
      }
    }

    3.2 Tread方法-获得当前线程对象【重点】

    方法名说明
    static Thread currentThread() 返回对当前正在执行的线程对象的引用
    package com.itheima.threaddemo5;
    public class Demo {
       public static void main(String[] args) {
           String name = Thread.currentThread().getName();
           System.out.println(name);
      }
    }

    3.3 线程休眠【应用】【重点】

    1.相关方法

    方法名说明
    static void sleep(long millis) 使当前正在执行的线程停留(暂停执行)指定的毫秒数

    代码演示

    package com.itheima.threaddemo6;

    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);
          }
      }
    }
    package com.itheima.threaddemo6;

    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();
      }
    }

    3.4 线程优先级【了解】

    1.优先级相关方法

    方法名说明
    final int getPriority() 返回此线程的优先级
    final void setPriority(int newPriority) 更改此线程的优先级线程默认优先级是5;线程优先级的范围是:1-10

    代码演示

    package com.itheima.threaddemo7;

    import java.util.concurrent.Callable;

    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 "线程执行完毕了";
      }
    }
    package com.itheima.threaddemo7;

    import java.util.concurrent.FutureTask;

    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();
      }
    }

     

    3.5 守护线程【了解】

    1.什么是守护线程?

    守护线程是程序运行的时候在后台提供一种通用服务的线程(保镖与主人)

    2.守护线程的特点?

    被守护的线程结束,不会立即结束,挣扎一会儿才结束

    3.如何使用守护线程?

    相关方法

    方法名说明
    void setDaemon(boolean on) 将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出

    代码演示

    package com.itheima.threaddemo8.example;


    public class MyThread1 extends Thread{

       @Override
       public void run() {
           for (int i = 0; i < 100; i++) {
               System.out.println(this.getName()+"---"+i);
          }
      }
    }
    package com.itheima.threaddemo8.example;


    public class MyThread1 extends Thread{

       @Override
       public void run() {
           for (int i = 0; i < 100; i++) {
               System.out.println(this.getName()+"---"+i);
          }
      }
    }
    package com.itheima.threaddemo8.example;


    public class Test {

       public static void main(String[] args) {
           MyThread1 t1 = new MyThread1();//保镖
           t1.setDaemon(true);
           MyThread2 t2 = new MyThread2();//主人
          t1.setName("保镖");
          t2.setName("主人");
          t2.start();
          t1.start();
      }
    }

    4.线程同步

    4.1卖票【难点】

    • 案例需求

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

    • 实现步骤

      • 定义一个类SellTicket实现Runnable接口,里面定义一个成员变量:private int tickets = 100;

      • 在SellTicket类中重写run()方法实现卖票,代码步骤如下

      • 判断票数大于0,就卖票,并告知是哪个窗口卖的

      • 卖了票之后,总票数要减1

      • 票卖没了,线程停止

      • 定义一个测试类SellTicketDemo,里面有main方法,代码步骤如下

      • 创建SellTicket类的对象

      • 创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称

      • 启动线程

    • 代码实现

      package com.itheima.threaddemo9;

      public class Ticket implements Runnable {
         //票的数量
         private int ticket = 100;

         @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 + "张票");
                    }
                }
             
      }
      }

      package com.itheima.threaddemo9;

      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();
        }
      }

    4.2 线程安全问题-原因分析【难点】

    1.卖票出现了问题

    • 相同的票出现了多次

    • 出现了负数的票

      出现问题的根本原因?

      线程执行的随机,在卖票的过程中CPU随机的执行了多条线程(也可以说是在卖票的过程中多条线程操作了共享数据ticket)

    4.3同步代码块解决数据安全问题【重点】

    1.如何解决上述问题呢?

    • 任意时刻只有一条线程可以操作共享变量

    • Java中如何解决?

    同步代码块格式:

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

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

    2.synchronized同步代码块的特点?

    默认情况下是打开的,只要有一个线程进去执行代码了,锁就会关闭

    当线程执行完出来时,锁才会自动打开

    3.什么样的情况下,会有线程安全问题

    3.1 多线程环境

    3.2 有共享数据

    3.3 有对共享数据的操作(增、删、改(除了long、double类的基本数据类型直接赋值)、查)

    代码演示

    package com.itheima.threaddemo9;
    public class Ticket implements Runnable {
       //票的数量
       private int ticket = 100;
       private Object obj = new Object();

       @Override
       public void run() {
           while(true){
               synchronized (obj){//多个线程必须使用同一把锁.
                   if(ticket <= 0){
                       //卖完了
                       break;
                  }else{
                       try {
                           Thread.sleep(100);
                      } catch (InterruptedException e) {
                           e.printStackTrace();
                      }
                       ticket--;
                       System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticket + "张票");
                  }
              }
          }
      }
    }

    package com.itheima.threaddemo9;

    public class Demo {
       public static void main(String[] args) {
           /*Ticket ticket1 = new Ticket();
           Ticket ticket2 = new Ticket();
           Ticket ticket3 = new Ticket();

           Thread t1 = new Thread(ticket1);
           Thread t2 = new Thread(ticket2);
           Thread t3 = new Thread(ticket3);*/

           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();
      }
    }

    4.4 线程安全问题-锁对象唯一【重点】

    1.锁对象为什么要唯一?

    不同线程如果锁的不是同一个对象,就解决不了线程的安全问题

    package com.itheima.threaddemo10;

    public class MyThread extends Thread {
       private static int ticketCount = 100;
       private static Object obj = new Object();

       @Override
       public void run() {
           while(true){
               synchronized (obj){ //就是当前的线程对象.
                   if(ticketCount <= 0){
                       //卖完了
                       break;
                  }else{
                       try {
                           Thread.sleep(100);
                      } catch (InterruptedException e) {
                           e.printStackTrace();
                      }
                       ticketCount--;
                       System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticketCount + "张票");
                  }
              }
          }
      }
    }

    package com.itheima.threaddemo010;

    public class Demo {
       public static void main(String[] args) {
           MyThread t1 = new MyThread();
           MyThread t2 = new MyThread();

           t1.setName("窗口一");
           t2.setName("窗口二");

           t1.start();
           t2.start();
      }
    }

    4.5同步方法解决数据安全问题【重点】

    (共4点)

    1.同步方法的格式

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

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

    2.同步方法的锁对象是什么呢?

    this

    public class MyRunnableCommon 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 (this){
                        if(ticketCount == 0){
                           break;
                        }else{
                            try {
                                Thread.sleep(10);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            ticketCount--;
                            System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticketCount + "张票");
                        }
                    }
                }
    
            }
        }
    
        private  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;
            }
        }
    }

    3.静态同步方法的格式

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

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

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

    类名.class

    代码演示

    package com.itheima.threaddemo011;
    
    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;
            }
        }
    }
    
    
    package com.itheima.threaddemo011;
    
    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();
        }
    }

    4.6 Lock锁【重点】(视频19 5‘’)

    1.如何手动开关锁呢?

    使用lock

    2.如何使用Lock?

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

    ReentrantLock构造方法

    方法名说明
    ReentrantLock() 创建一个ReentrantLock的实例

    加锁解锁方法

    方法名说明
    void lock() 获得锁
    void unlock() 释放锁

    代码演示

    package com.itheima.threaddemo012;
    
    import java.util.concurrent.locks.ReentrantLock;
    
    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();
                }
                // }
            }
        }
    }
    
    package com.itheima.threaddemo012;
    
    public class Demo {
        public static void main(String[] args) {
            /*Ticket ticket1 = new Ticket();
            Ticket ticket2 = new Ticket();
            Ticket ticket3 = new Ticket();
    
            Thread t1 = new Thread(ticket1);
            Thread t2 = new Thread(ticket2);
            Thread t3 = new Thread(ticket3);*/
    
            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();
        }
    }

    4.7 死锁【了解】

    1.什么是死锁?(吃饭)

    线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行

    代码演示

    5.生产者消费者

    5.1 生产者和消费者思路分析【难点】

    5.2生产者和消费者案例【难点】

    Object类的等待和唤醒方法

    方法名说明
    void wait() 导致当前线程等待同时释放锁,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法
    void notify() 唤醒正在等待单个线程,并不立即释放锁
    void notifyAll() 唤醒正在等待所有线程,并不立即释放锁

    案例需求

    • 桌子类(Desk):定义表示包子数量的变量,定义锁对象变量,定义标记桌子上有无包子的变量

    • 生产者类(Cooker):实现Runnable接口,重写run()方法,设置线程任务

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

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

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

    • 消费者类(Foodie):实现Runnable接口,重写run()方法,设置线程任务

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

      2.如果没有包子,就进入等待状态,如果有包子,就消费包子

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

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

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

      分别开启两个线程

    代码实现

    package com.itheima.threaddemo014;
    
    public class Desk {
    
        //定义一个标记
        //true 就表示桌子上有汉堡包的,此时允许吃货执行
        //false 就表示桌子上没有汉堡包的,此时允许厨师执行
        public static boolean flag = false;
    
        //汉堡包的总数量
        public static int count = 10;
    
        //锁对象
        public static final Object lock = new Object();
    }
    
    package com.itheima.threaddemo014;
    
    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();
                            }
                        }
                    }
                }
            }
        }
    }
    
    package com.itheima.threaddemo014;
    
    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();
                            }
                        }
                    }
                }
            }
    
        }
    }

    5.3生产者和消费者案例优化【难点】

    • 需求

      • 将Desk类中的变量,采用面向对象的方式封装起来

      • 生产者和消费者类中构造方法接收Desk类对象,之后在run方法中进行使用

      • 创建生产者和消费者线程对象,构造方法中传入Desk类对象

      • 开启两个线程

    • 代码实现

      package com.itheima.threaddemo015;
      
      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 +
                      '}';
        }
      }
      
      package com.itheima.threaddemo015;
      
      public class Cooker extends Thread {
      
          private Desk desk;
      
          public Cooker(Desk desk) {
              this.desk = desk;
          }
      //    生产者步骤:
      //            1,判断桌子上是否有汉堡包
      //    如果有就等待,如果没有才生产。
      //            2,把汉堡包放在桌子上。
      //            3,叫醒等待的消费者开吃。
      	
              //套路:
                  //1. while(true)死循环
                  //2. synchronized 锁,锁对象要唯一
                  //3. 判断,共享数据是否结束. 结束
                  //4. 判断,共享数据是否结束. 没有结束
          @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();
                              }
                          }
                      }
                  }
              }
          }
      }
      
      
      package com.itheima.threaddemo015;
      
      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 +
                      '}';
          }
      }
      
      package com.itheima.threaddemo015;
      
      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();
                              }
                          }
                      }
                  }
              }
      
          }
      }

    5.4阻塞队列基本使用【重点】

    (共4点)

    1.什么是阻塞队列?

    阻塞队列是一个在队列基础上又支持了两个附加操作的队列

    2.附加的方法

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

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

    3.阻塞队列继承结构

    4.常见BlockingQueue实现类

    ArrayBlockingQueue: 底层是数组,有界

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

    代码示例

    package com.itheima.threaddemo016;
    
    import java.util.concurrent.ArrayBlockingQueue;
    
    public class Demo {
        public static void main(String[] args) throws InterruptedException {
             // 创建阻塞队列的对象,容量为 1
             ArrayBlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue<>(1);
    
             // 存储元素
             arrayBlockingQueue.put("汉堡包");
    
             // 取元素
             System.out.println(arrayBlockingQueue.take());
             System.out.println(arrayBlockingQueue.take()); // 取不到会阻塞
    
             System.out.println("程序结束了");
        }
    }

    5.5阻塞队列实现等待唤醒机制【难点】

    案例需求

    • 生产者类(Cooker):实现Runnable接口,重写run()方法,设置线程任务

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

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

      3.打印添加结果

    • 消费者类(Foodie):实现Runnable接口,重写run()方法,设置线程任务

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

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

      3.打印获取结果

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

      创建阻塞队列对象

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

      分别开启两个线程

    代码实现

    package com.itheima.threaddemo016;
    
    import java.util.concurrent.ArrayBlockingQueue;
    
    public class Demo {
        public static void main(String[] args) throws InterruptedException {
            ArrayBlockingQueue<String> bd = new ArrayBlockingQueue<>(1);
            Foodie f = new Foodie(bd);
            Cooker c = new Cooker(bd);
            f.start();
            c.start();
        }
    }
    package com.itheima.threaddemo016;
    
    import com.itheima.threaddemo015.Desk;
    
    import java.util.concurrent.ArrayBlockingQueue;
    
    public class Cooker extends Thread {
    
        private ArrayBlockingQueue<String> bd;
    
        public Cooker(ArrayBlockingQueue<String> bd) {
            this.bd = bd;
        }
        @Override
        public void run() {
            while (true) {
                try {
                    bd.put("汉堡包");
                    System.out.println("厨师放入一个汉堡包");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    package com.itheima.threaddemo016;
    
    import com.itheima.threaddemo015.Desk;
    
    import java.util.concurrent.ArrayBlockingQueue;
    
    public class Foodie extends Thread {
        private ArrayBlockingQueue<String> bd;
    
        public Foodie(ArrayBlockingQueue<String> bd) {
            this.bd = bd;
        }
    
        @Override
        public void run() {
            while (true) {
                try {
                    String take = bd.take();
                    System.out.println("吃货将" + take + "拿出来吃了");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
    
        }
    }

    总结:

    在java中如何实现等待和唤醒机制?

    1.wait和notify()或notifyAll()

    wait:让线程等待,同时立即释放锁 --->sleep():让线程休眠,但是不会释放锁

    notify()或notifyAll(): 唤醒等待的线程,但是不会立即释放锁

    2.阻塞队列

    put:存,如果队例满的话,会阻塞(等待)

    take:取,如果取不到,会阻塞(等待)

                            扩展练习

    题目1

    编写程序,创建两个线程对象,一根线程循环输出“播放背景音乐”,另一根线程循环输出“显示画面”; 要求:

    1: 1个线程使用Runnable接口的匿名内部类实现

    2: 另一个线程使用lambda实现

    效果:

    参考答案:

    package day13.No_1;

    public class Demo {
       public static void main(String[] args) {
           //匿名内部类实现
           new Thread(new Runnable() {
               @Override
               public void run() {
                   while (true) {
                       System.out.println("播放背景音乐!");
                  }
              }
          }).start();
           //lambda实现
           new Thread(() -> {
               while (true) {
                   System.out.println("显示画面!");
              }
          }).start();
      }
    }

    题目2

    3.请使用继承Thread类的方式定义一个线程类,在run()方法中循环10次,每1秒循环1次,每次循环按“yyyy-MM-dd HH:mm:ss”的格式打印当前系统时间。 请定义测试类,并定义main()方法,启动此线程,观察控制台打印。

    要求:

    1: 使用匿名内部类配合SimpleDateFormat和Date实现

    2: 使用lambda配合LocalDateTime和DateTimeFormatter实现

    效果:

    参考答案:

    package day13.No_2;

    import java.text.SimpleDateFormat;
    import java.time.LocalDateTime;
    import java.time.format.DateTimeFormatter;
    import java.util.Date;

    public class Demo {
       public static void main(String[] args) {
     /*
     //方式一内部类实现
           new Thread(new Runnable() {
               @Override
               public void run() {
                   for (int i = 0; i < 10; i++) {
                       try {
                           Thread.sleep(1000);
                       } catch (InterruptedException e) {
                           e.printStackTrace();
                       }
                       Date date = new Date();
                       SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                       String format = sdf.format(date);
                       System.out.println(format);
                   }
               }
           }).start();
           */
     //方式二:lambda实现
           new Thread(()->{
               for (int i = 0; i < 10; i++) {
                   try {
                       Thread.sleep(1000);
                  } catch (InterruptedException e) {
                       e.printStackTrace();
                  }
                   LocalDateTime now = LocalDateTime.now();
                   DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
                   String format = dateTimeFormatter.format(now);
                   System.out.println(format);
              }
          }).start();
      }
    }

     运行效果:

    题目3

    请编写多线程应用程序,模拟多个人通过一个山洞: (1).这个山洞每次只能通过一个人,每个人通过山洞的时间为1秒; (2).创建10个线程,同时准备过此山洞,并且定义一个变量用于记录通过隧道的人数。显示每次通过山洞人的姓名,和通过顺序;

    要求:

    保证安全问题,不能出现多个人同时通过山洞的现象;(必须逐一通过)

    效果:

    参考答案:

    线程文件:

    package day13.No_3;

    public class MyRunnable implements Runnable {
       private static int count = 10;
       private final static Object obj = new Object();
       private static int i = 1;

       @Override
       public void run() {
           synchronized (obj) {
               try {
                   Thread.sleep(1000);
              } catch (InterruptedException e) {
                   e.printStackTrace();
              }
               count--;
               System.out.println(Thread.currentThread().getName() + "通过山洞,他是第" + i + "个通过");
               i++;
          }
      }
    }


    测试文件:

    package day13.No_3;

    public class Demo {
       public static void main(String[] args) {
         //此处可以使用for循环创建线程
           MyRunnable mr = new MyRunnable();
           Thread tr1 = new Thread(mr, "线程1");
           tr1.start();
           Thread tr2 = new Thread(mr, "线程2");
           tr2.start();
           Thread tr3 = new Thread(mr, "线程3");
           tr3.start();
           Thread tr4 = new Thread(mr, "线程4");
           tr4.start();
           Thread tr5 = new Thread(mr, "线程5");
           tr5.start();
           Thread tr6 = new Thread(mr, "线程6");
           tr6.start();
           Thread tr7 = new Thread(mr, "线程7");
           tr7.start();
           Thread tr8 = new Thread(mr, "线程8");
           tr8.start();
           Thread tr9 = new Thread(mr, "线程9");
           tr9.start();
           Thread tr10 = new Thread(mr, "线程10");
           tr10.start();

      }
    }

     运行效果:

    题目4

    拼手速抽奖案例.

    1.现有一个集合装了10个奖品在里面,分别是:{"电视机","电冰箱","电脑","游戏机","洗衣机","空调","手机","平板电脑","电动车","电饭煲"};

    2.假如有3个人同时去抽这10个奖品.最后打印出来.三个人各自都抽到了什么奖品.

    例如:

    张三: “电视机”,”电冰箱”,”电脑”,”游戏机”,”洗衣机”

    李四: ”空调”,”手机”,”平板电脑”,

    王五: ”电动车”,”电饭煲

    要求:

    1:3个人同时开始抽奖,每次抽奖需要使用0.5秒才能完成抽奖;

    2:需要控制住同一个奖项不能同时被多个人抽走;

    效果:

    参考答案:

    线程类

    package day13.No_4;

    import java.util.ArrayList;
    import java.util.List;
    import java.util.Random;

    public class MyRunnable implements Runnable {
       private static ArrayList<String>list=new ArrayList<>(List.of("电视机","电冰箱","电脑","游戏机","洗衣机","空调","手机","平板电脑","电动车","电饭煲"));
       private static Object object=new Object();
       @Override
       public void run() {
           while (list.size()>0){
               synchronized (object){
                   if (list.size()==0){
                       return;
                  }
                   try {
                       Thread.sleep(500);
                  } catch (InterruptedException e) {
                       e.printStackTrace();
                  }
                   String name = list.remove(new Random().nextInt(list.size()));
                   System.out.println(Thread.currentThread().getName()+"抽到了"+name);
              }
          }
      }
    }

    测试类:

    package day13.No_4;

    public class Demo {
       public static void main(String[] args) {
           MyRunnable mr = new MyRunnable();
           Thread t1 = new Thread(mr, "张三");
           Thread t2 = new Thread(mr, "李四");
           Thread t3 = new Thread(mr, "王五");
           t1.start();
           t2.start();
           t3.start();
      }
    }

    运行效果:

     

     

  • 相关阅读:
    快速开发框架-Lion Framework
    安装redis 及常见问题
    Redis安装手册
    关于TbSchedule任务调度管理框架的整合部署1
    关于TbSchedule任务调度管理框架的整合部署
    zookeeper实战:SingleWorker代码样例
    基于ZooKeeper的分布式Session实现
    基于ZooKeeper构建大规模配置系统
    解决克隆centos虚拟机后ip配置失败的问题
    Spark学习资料
  • 原文地址:https://www.cnblogs.com/859630097com/p/14227480.html
Copyright © 2011-2022 走看看