程序员子龙(Java面试 + Java学习) 程序员子龙(Java面试 + Java学习)
首页
学习指南
工具
开源项目
技术书籍

程序员子龙

Java 开发从业者
首页
学习指南
工具
开源项目
技术书籍
  • 基础

  • JVM

  • Spring

  • 并发编程

    • 并发和并行
    • 什么是多线程
      • Java 并发容器有哪些?
      • Java的Future机制详解
      • 什么是AQS
      • 一文搞懂 ThreadLocal
      • java 阻塞队列 详细介绍
      • java线程池使用最全详解
      • 面试官问我什么是JMM
      • CountDownLatch、Semaphore和CyclicBarrier
      • Java:线程的六种状态及转化
    • Mybatis

    • 网络编程

    • 数据库

    • 缓存

    • 设计模式

    • 分布式

    • 高并发

    • SpringBoot

    • SpringCloudAlibaba

    • Nginx

    • 面试

    • 生产问题

    • 系统设计

    • 消息中间件

    • Java
    • 并发编程
    程序员子龙
    2024-01-29
    目录

    什么是多线程

    # 线程与进程

    ● 进程:是指⼀个内存中运行的应用程序,每个进程都有⼀个独立的内存空间,⼀个应用程序可以同时运行多个进程;进程也是程序的⼀次执行过程,是系统运行程序的基本单位;系统运行⼀个程序即是⼀个进程从创建、运行到消亡的过程。

    进程是系统进行资源分配和调度的独立单位。每一个进程都有它自己的内存空间和系统资源

    ● 线程:线程是进程中的⼀个执行单元,负责当前进程中程序的执行,⼀个进程中至少有⼀个线程。 ⼀个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。

    ⼀个程序运行后至少有⼀个进程,⼀个进程中可以包含多个线程。

    我们可以再电脑底部任务栏,右键----->打开任务管理器,可以查看当前任务的进程:

    image-20220108171950087

    那系统有了进程这么一个概念了,进程已经是可以进行资源分配和调度了,为什么还要线程呢?

    为使程序能并发执行,系统必须进行以下的一系列操作:

    • 创建进程,系统在创建一个进程时,必须为它分配其所必需的、除处理机以外的所有资源,如内存空间、I/O设备,以及建立相应PCB;

    • 撤消进程,系统在撤消进程时,又必须先对其所占有的资源执行回收操作,然后再撤消PCB;

    • 进程切换,对进程进行上下文切换时,需要保留当前进程的CPU环境,设置新选中进程的CPU环境,因而须花费不少的处理机时间。

    image-20220108173017093

    可以看到进程实现多处理机环境下的进程调度,分派,切换时,都需要花费较大的时间和空间开销

    引入线程主要是**为了提高系统的执行效率,减少处理机的空转时间和调度切换的时间,以及便于系统管理。**使OS具有更好的并发性。

    简单来说:进程实现多处理非常耗费CPU的资源,而我们引入线程是作为调度和分派的基本单位(取代进程的部分基本功能**【调度】**)。

    # 并发与并行

    ● 并发:指两个或多个事件在同⼀个时间段内发生。

    ● 并行:指两个或多个事件在同⼀时刻发生(同时发生)。

    在操作系统中,安装了多个程序,并行指的是在⼀段时间内宏观上有多个程序同时运行,这在单 CPU 系统中,每⼀时刻只能有⼀道程序执行,即微观上这些程序是分时的交替运行,只不过是给⼈的感觉是同时运行,那是因为分时交替运行的时间是非常短的。

    而在多个 CPU 系统中,则这些可以并发执行的程序便可以分配到多个处理器上(CPU),实现多任务 并行执行,即利⽤每个处理器来处理⼀个可以并发执行的程序,这样多个程序便可以同时执行。目前电脑市场上说的多核 CPU,便是多核处理器,核 越多,并行处理的程序越多,能大大的提高电脑运行的 效率。

    注意:单核处理器的计算机肯定是不能并行的处理多个任务的,只能是多个任务在单个CPU上 并发运行。同理,线程也是⼀样的,从宏观⻆度上理解线程是并行运行的,但是从微观⻆度上分析却是串行运行的,即⼀个线程⼀个线程的去运行,当系统只有⼀个CPU时,线程会以某种顺序执行多个线程,我们把这种情况称之为线程调度。

    # 线程生命周期

    1、新建状态(New)

    当线程对象对创建后,即进入了新建状态;

    2、就绪状态(Runnable)

    线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了 start() 此线程立即就会执行;

    3、运行状态(Running)

    当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,可运行状态(runnable)的线程获得了cpu 时间片(timeslice),即进入到运行状态。注:就绪状态是进入到运行状态的唯⼀入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;

    注意:java线程将操作系统中的就绪和运行两种状态笼统的称为“运行状态”

    4、阻塞状态(Blocked)

    处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,即让出了cpu timeslice,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才有机会再次被CPU调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种:

    • 等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
    • 同步阻塞:线程在获取同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
    • 其他阻塞:通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep() 状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

    5、等待状态(Waiting)

    进入等待状态表示当前线程需要等待其他线程做出一些特定的动作(通知或中断)。

    6、超时等待(Time_Waiting)

    超时等待不等同于等待状态,它是可以在指定的时间自行返回的。

    7、终止状态(TERMINATED)

    线程执行完了或者因异常退出了run()方法,该线程结束生命周期。 死亡的线程不可再次复生。

    image-20220108201242075

    image-20220108202705763

    # 多线程状态之间的转换

    就绪状态转换为运行状态:当此线程得到处理器资源;

    运行状态转换为就绪状态:当此线程主动调用yield()方法或在运行过程中失去处理器资源。

    运行状态转换为死亡状态:当此线程线程执行体执行完毕或发生了异常。

    此处需要特别注意的是:当调用线程的yield()方法时,线程从运行状态转换为就绪状态,但接下来CPU调度就绪状态中的哪个线程具有⼀定的随机性,因此,可能会出现A线程调用了yield()方法后,接下来 CPU仍然调度了A线程的情况。

    下面就来讲解与线程生命周期相关的方法~

    # 线程常用方法

    image-20220108225335552

    # 线程的优先级

    可以通过传递参数给线程的 setPriority() 来设置线程的优先级别

    调整线程优先级:Java线程有优先级,优先级高的线程会获得较多的运行机会。优先级 : 只能反映线程 的 中或者是 紧急程度 , 不能决定 是否⼀定先执行行

    Java线程的优先级用整数表示,取值范围是1~10,Thread类有以下三个静态常量:

    //线程可以具有的最高优先级,取值为10。
    static int MAX_PRIORITY 
    //线程可以具有的最低优先级,取值为1。 
    static int MIN_PRIORITY 
    //分配给线程的默认优先级,取值为5。
    static int NORM_PRIORITY 
    
    1
    2
    3
    4
    5
    6
    class PriorityThread extends Thread{
        @Override
        public void run() {
            for(int i=0;i<50;i++) {
                System.out.println(Thread.currentThread().getName()+"============"+i);
            }
        }
    }
    
    public class TestPriority {
        public static void main(String[] args) {
            PriorityThread p1 = new PriorityThread();
            p1.setName("P1线程");
    
            PriorityThread p2 = new PriorityThread();
            p2.setName("P2线程");
    
            PriorityThread p3 = new PriorityThread();
            p3.setName("P3线程");
    
            p1.setPriority(Thread.MAX_PRIORITY);
            p3.setPriority(Thread.MIN_PRIORITY);
            
            p1.start();
            p2.start();
            p3.start();
    
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29

    # sleep方法

    sleep()使当前线程进入阻塞状态,在指定时间内不会执行,但不会释放“锁标志”。

    调用sleep方法会进入计时等待状态,等时间到了,进入的是就绪状态而并非是运行状态!

     try {
                Thread.sleep(1000*2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
    
    1
    2
    3
    4
    5
    6

    # yield方法

    暂停当前正在执行的线程对象,并执行其他线程。

    yield()只是使当前线程重新回到可运行状态,所以执行yield()的线程有可能在进入到可运行状态后马上又被执行。

    yield()只能使同优先级或更高优先级的线程有执行的机会。

    使用 yield() 的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证 yield() 达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。

    image-20220108203529882

    class Task1 implements Runnable{
    
        @Override
        public void run() {
            for (int i = 0;i < 100;i++){
                System.out.println("A:"+i);
            }
        }
    }
    
    class Task2 implements Runnable{
    
        @Override
        public void run() {
            for (int i = 0;i < 10;i++){
                System.out.println("B:"+i);
                //让步
                Thread.yield();
            }
        }
    }
    
    public class TestYield {
        public static void main(String[] args) {
            //匿名对象,这个方法只需要使用一次
            new Thread(new Task1()).start();
            new Thread(new Task2()).start();
    
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30

    image-20220108212641915

    从输出结果可以虽然线程B让步了,但是也不是线程A执行完了,线程B才执行。

    sleep()和yield()的区别

    这是一个面试的考点。

    sleep()使当前线程进入停滞状态,所以执行sleep()的线程在指定的时间内肯定不会被执行;

    yield()只是使当前线程重新回到可运行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。

    sleep 方法使当前运行中的线程睡眼⼀段时间,进入不可运行状态,这段时间的长短是由程序设定的,yield 方法使当前线程让出 CPU 占有权,但让出的时间是不可设定的。实际上,yield()方法对应了如下操作:先检测当前是否有相同优先级的线程处于同可运行状态,如有,则把 CPU的占有权交给此线程,否则,继续运行原来的线程。所以yield()方法称为“退让”,它把运行机会让给了同等优先级的其他线程。

    另外,sleep 方法允许较低优先级的线程获得运行机会,但 yield() 方法执行时,当前线程仍处在可运行状态,所以,不可能让出较低优先级的线程些时获得 CPU 占有权。在⼀个运行系统中, 如果较高优先级的线程没有调用 sleep 方法,又没有受到 I\O 阻塞,那么,较低优先级线程只能等待所有较高优先级的线程运行结束,才有机会运行。

    # join方法

    join() 方法的作用是调用线程等待该线程完成后,才能继续往下运行。

    image-20220108211036052

    join是Thread类的⼀个方法,启动线程后直接调用,即join()的作用是:“等待该线程终止”,这⾥需要理解的就是该线程是指的主线程等待子线程的终止。也就是在子线程调用了join()方法后面的代码,只有等到子线程结束了才能执行。

    image-20220108211254567

    为什么要用join()方法

    在很多情况下,主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了。

    class JoinThread extends Thread{
        public JoinThread(String name){
            super(name);
        }
        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName()+"打印---->"+i);
            }
        }
    }
    
    public class TestJoin {
        public static void main(String[] args) {
            System.out.println("主线程开始执行.....");
    
            JoinThread joinThread = new JoinThread("新加入的线程");
            joinThread.start();
            try {
                joinThread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            System.out.println("主线程结束执行.....");
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27

    主线程开始执行..... 新加入的线程打印---->0 新加入的线程打印---->1 新加入的线程打印---->2 新加入的线程打印---->3 新加入的线程打印---->4 主线程结束执行.....

    # 守护线程

    守护线程.setDaemon(true):设置守护线程

    线程有两类:用户线程(前台线程)、守护线程(后台线程)

    如果程序中所有前台线程都执行完毕了,后台线程会自动结束

    垃圾回收器线程属于守护线程

    守护线程有一个特点:

    • 当别的用户线程执行完了,虚拟机就会退出,守护线程也就会被停止了。

    • 也就是说:守护线程作为一个服务线程,没有服务对象就没有必要继续运行了

    使用线程的时候要注意的地方

    在线程启动前设置为守护线程,方法是setDaemon(boolean on)

    使用守护线程不要访问共享资源(数据库、文件等),因为它可能会在任何时候就挂掉了。

    守护线程中产生的新线程也是守护线程

    class DeamonThread extends Thread{
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println("守护线程打印:"+i);
            }
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    public class TestDeamon {
        public static void main(String[] args) {
            //启动一个守护线程
            DeamonThread deamonThread = new DeamonThread();
            deamonThread.setDaemon(true);//是守护线程
            deamonThread.start();
    
            for (int i = 0; i < 10; i++) {
                System.out.println("主线程打印:"+i);
            }
    
    
        }
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29

    image-20220108230324963

    从输出结果可以看出,主线程结束后守护线程也结束了!

    # 线程中止

    线程自然终止

    要么是 run 执行完成了,要么是抛出了一个未处理的异常导致线程提前结束。

    stop

    暂停、恢复和停止操作对应在线程 Thread 的 API 就是 suspend()、resume()和stop()。但是这些 API 是过期的,也就是不建议使用的。不建议使用的原因主要有:以 suspend()方法为例,在调用后,线程不会释放已经占有的资源(比如 锁),而是占有着资源进入睡眠状态,这样容易引发死锁问题。同样,stop()方法在终结一个线程时不会保证线程的资源正常释放,通常是没有给予线程完成资源释放工作的机会,因此会导致程序可能工作在不确定状态下。正因为 suspend()、 resume()和 stop()方法带来的副作用,这些方法才被标注为不建议使用的过期方法。

    现在已经没有强制线程终止的方法了!

    中断

    安全的中止则是其他线程通过调用某个线程 A 的 interrupt()方法对其进行中断操作, 中断好比其他线程对该线程打了个招呼,“A,你要中断了”,不代表线程 A 会立即停止自己的工作,同样的 A 线程完全可以不理会这种中断请求。 也就是说:Java设计者实际上是想线程自己来终止,通过上面的信号,就可以判断处理什么业务了。具体到底中断还是继续运行,应该由被通知的线程自己处理。

    Thread t1 = new Thread( new Runnable(){
        public void run(){
            // 若未发生中断,就正常执行任务
            while(!Thread.currentThread.isInterrupted()){
                // 正常任务代码……
            }
            // 中断的处理代码……
            doSomething();
        }
    } ).start();
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    线程通过检查自身的中断标志位是否被置为 true 来进行响应, 线程通过方法 **isInterrupted()**来进行判断是否被中断,也可以调用静态方法 **Thread.interrupted()**来进行判断当前线程是否被中断,不过 Thread.interrupted() 会同时将中断标识位改写为 false。

    如果一个线程处于了阻塞状态(如线程调用了 thread.sleep、thread.join、 thread.wait 等),则在线程在检查中断标示时如果发现中断标示为 true,则会在这些阻塞方法调用处抛出 InterruptedException 异常,并且在抛出异常后会立即将线程的中断标示位清除,即重新设置为 false。

    不建议自定义一个取消标志位来中止线程的运行。因为 run 方法里有阻塞调用时会无法很快检测到取消标志,线程必须从阻塞调用返回后,才会检查这个取 消标志。这种情况下,使用中断会更好,因为:

    一、一般的阻塞方法,如 sleep 等本身就支持中断的检查,

    二、检查中断位的状态和检查取消标志位没什么区别,用中断位的状态还可以避免声明取消标志位,减少资源的消耗。

    注意:处于死锁状态的线程无法被中断

    再次说明:调用interrupt()并不是要真正终止掉当前线程,仅仅是设置了一个中断标志。这个中断标志可以给我们用来判断什么时候该干什么活!什么时候中断由我们自己来决定,这样就可以安全地终止线程了!

    interrupt线程中断还有另外两个方法(检查该线程是否被中断):

    • 静态方法interrupted()-->会清除中断标志位

    • 实例方法isInterrupted()-->不会清除中断标志位

      public static void main(String[] args) {
            Main main = new Main();
    
            // 创建线程并启动
            Thread t = new Thread(main.runnable);
            System.out.println("This is 主方法");
            t.start();
    
            try {
    
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                System.out.println("In main");
                e.printStackTrace();
            }
    
            // 设置中断
            t.interrupt();
        }
    
        Runnable runnable = () -> {
            int i = 0;
            try {
                while (i < 1000) {
    
                    // 睡个半秒钟我们再执行
                    Thread.sleep(500);
    
                    System.out.println(i++);
                }
            } catch (InterruptedException e) {
    
                // 判断该阻塞线程是否还在
                System.out.println(Thread.currentThread().isAlive());
    
                // 判断该线程的中断标志位状态
                System.out.println(Thread.currentThread().isInterrupted());
    
                System.out.println("In Runnable");
                e.printStackTrace();
            }
        };
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42

    This is 主方法 0 1 2 3 4 true false In Runnable java.lang.InterruptedException: sleep interrupted

    执行流程是这样的:

    image-20220108224500708

    # 线程通信

    多个线程在处理同⼀个资源,但是处理的动作(线程的任务)却不相同。

    多个线程并发执行时, 在默认情况下CPU是随机切换线程的,当我们需要多个线程来共同完成⼀件任务,并且我们希望他们有规律的执行, 那么多线程之间需要⼀些协调通信,以此来帮我们达到多线程共同操作⼀份数据。

    如何保证线程间通信有效利用资源 ?

    多个线程在处理同⼀个资源,并且任务不同时,需要线程通信来帮助解决线程之间对同⼀个变量的使用或操作。 就是多个线程在操作同⼀份数据时, 避免对同⼀共享变量的争夺。也就是我们需要通过⼀定的⼿段使各个线程能有效的利用资源。而这种手段即—— 等待唤醒机制。

    image-20220112222324639

    什么是等待唤醒机制

    这是多个线程间的⼀种协作机制。谈到线程我们经常想到的是线程间的竞争(race),比如去争夺锁,线程间也会有协作机制。

    就是在⼀个线程进行了规定操作后,就进入等待状态(wait()), 等待其他线程执行完他们的指定代码过后再将其唤醒

    (notify());在有多个线程进行等待时, 如果需要,可以使用 notifyAll()来唤醒所有的等待线程。

    指一个线程 A 调用了对象 O 的 wait()方法进入等待状态,而另一个线程 B 调用了对象 O 的 notify()或者 notifyAll()方法,线程 A 收到通知后从对象 O 的 wait() 方法返回,进而执行后续操作。上述两个线程通过对象 O 来完成交互,而对象 上的 wait()和 notify/notifyAll()的关系就如同开关信号一样,用来完成等待方和通知方之间的交互工作。

    线程通信方法

    方法 说明
    public final void wait() 释放锁,进入等待队列
    public final void wait(long timeout) 在超过指定的时间前,释放锁,进入等待队列
    public final void notify() 随机唤醒、通知⼀个线程
    public final void notifyAll() 唤醒、通知所有线程

    wait()

    调用该方法的线程进入 WAITING 状态,只有等待另外线程的通知或被中断才会返回.需要注意,调用 wait()方法后,会释放对象的锁。它还要等着别的线程执行⼀个特别的动作,也即 是“通知(notify)”在这个对象上等待的线程从wait set 中释放出来,重新进⼊到调度队列

    (ready queue)中 。

    wait (long,int)

    对于超时时间更细粒度的控制,可以达到纳秒,还没有被notify唤醒,就会自动醒来 。

    notify()

    通知一个在对象上等待的线程,使其从 wait 方法返回,而返回的前提是该线程获取到了对象的锁,没有获得锁的线程重新进入 WAITING 状态。

    notifyAll()

    通知所有等待在该对象上的线程。

    等待和通知的标准范式

    等待方遵循如下原则:

    1)获取对象的锁。

    2)如果条件不满足,那么调用对象的 wait()方法,被通知后仍要检查条件。

    3)条件满足则执行对应的逻辑。

    synchronized(对象){
        while(条件不满足){
              对象.wait();
        }          
         对应的处理逻辑
    }
    
    1
    2
    3
    4
    5
    6

    通知方遵循如下原则:

    1)获得对象的锁。

    2)改变条件。

    3)通知所有等待在对象上的线程。

    synchronized(对象){
        改变条件;
        对象.notifyAll();         
    }
    
    1
    2
    3
    4

    在调用 wait()、notify()系列方法之前,线程必须要获得该对象的对象级别锁,即只能在同步方法或同步块中调用 wait()方法、notify()系列方法,进入 wait()方法后,当前线程释放锁,在从 wait()返回前,线程与其他线程竞争重新获得锁,执行 notify()系列方法的线程退出调用了 notifyAll 的 synchronized 代码块的时候后,他们就会去竞争。如果其中一个线程获得了该对象锁,它就会继续往下执行,在它退出 synchronized 代码块,释放锁后,其他的已经被唤醒的线程将会继续竞争获取该锁,一直进行下去,直到所有被唤醒的线程都执行完毕。

    notify 和 notifyAll 应该用谁 ?

    尽可能用 notifyall(),谨慎使用 notify(),因为 notify()只会唤醒一个线程,我们无法确保被唤醒的这个线程一定就是我们需要唤醒的线程。

    调用wait和notify方法需要注意的细节

    1、wait方法与notify方法必须要由同⼀个锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同⼀个锁对象调用的wait方法后的线程。

    2、wait方法与notify方法是属于Object类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继承了Object类的。

    3、wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用这2个方法。

    注意:

    哪怕只通知了⼀个等待的线程,被通知线程也不能立即恢复执行,因为它当初中断的地方是在同步块 内,而此刻它已经不持有锁,所以她需要再次尝试去获取锁(很可能⾯临其它线程的竞争),成功后才 能在当初调用 wait 方法之后的地方恢复执行。

      public static void main(String[] args) {
            Object lock = new Object();//这个就是协调者的角色,
    
            new Thread(new Runnable() {
                @Override
                public void run() {
    
                    //进入等待
                    synchronized (lock){
                        System.out.println("顾客1线程:1、点餐....");
                        try {
                            lock.wait();
                            //lock.wait(4000);//会等待指定的时间,如果没有被唤醒,那么会自己唤醒,执行后面的代码
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("顾客1线程:3、开始吃....");
                    }
    
                }
            },"顾客线程1").start();
    
    
            new Thread(new Runnable() {
                @Override
                public void run() {
    
                    //进入等待
                    synchronized (lock){
                        System.out.println("顾客2线程:1、点餐....");
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("顾客2线程:3、开始吃....");
                    }
    
                }
            },"顾客线程2").start();
    
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    //等待2秒时间
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
    
                    //通知顾客
                    synchronized (lock){
                        System.out.println("2、老板做好了,交给顾客.....");
                        lock.notifyAll();//通知所有等待的线程
                    }
                }
            },"老板线程").start();
    
    
        }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62

    顾客1线程:1、点餐.... 顾客2线程:1、点餐.... 2、老板做好了,交给顾客..... 顾客2线程:3、开始吃.... 顾客1线程:3、开始吃....

    上次更新: 2024/03/11, 15:54:57
    并发和并行
    Java 并发容器有哪些?

    ← 并发和并行 Java 并发容器有哪些?→

    最近更新
    01
    一个注解,优雅的实现接口幂等性
    11-17
    02
    MySQL事务(超详细!!!)
    10-14
    03
    阿里二面:Kafka中如何保证消息的顺序性?这周被问到两次了
    10-09
    更多文章>
    Theme by Vdoing | Copyright © 2024-2024

        辽ICP备2023001503号-2

    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式