Java并发05:趣解Thread和Object类中线程相关方法

1,043 阅读12分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第5天,点击查看活动详情

学习MOOC视频记录的笔记

回顾:

 实现多线程通常有两种方式,本质上是一种方式,外在形式有多种方式
 应该用start()方法启动线程而不是run()方法
 停止线程实际上是一种规范,不仅需要我们让它停止,还需要被停止的线程配合我们
 线程一生的6种状态记住图就好了

1.为什么线程通信的方法 wait()notify()notifyAll()被定义在 Object 类里?而 sleep定义在 Thread 类里?

2.用 3 种方式实现生产者模式

3.Java SE8Java1.8JDK8 是什么关系,是同一个东西吗?

4.joinsleepwait期间线程的状态分别是什么?为什么?

ThreadObject类中的重要方法详解

1.方法概览

方法名介绍
Threadsleep 相关本表格的相关,指的的重载方法,也就是方法名相同,但是参数不同,例如 sleep 有多个方法,只是参数不同,实际作用大同小异
join等待其他线程执行完毕
yield 相关放弃已经获取到的 CPU 资源
currentThread获取当前执行线程的引用
start,run 相关启动线程相关
interrupt 相关中断线程
stop(),suspend(),resume() 相关已废弃
Objectwait/notify/notifyAll 相关让线程暂时休息和唤醒

2.wait, notify, notifyAll 方法详解

2.1 作用、用法

可以控制一些线程的休息与唤醒

阻塞阶段

想让一个或多个线程去休息一下,后续需要它或者条件成熟的时候再去唤醒它。执行 wait 方法的时候必须拥有对象的Monitor 锁。调用者进入阻塞状态,不再被调度。

唤醒阶段

直到以下4种情况之一发生时,才会被唤醒

  1. 另一个线程调用这个对象的 notify() 方法且刚好被唤醒的是本线程;
  2. 另一个线程调用这个对象的 notifyAll() 方法;
  3. 过了 wait(long timeout) 规定的超时时间,如果传入 0 就是永久等待;
  4. 线程自身调用了 interrupt() 【类似之前在sleep 的时候调用 interrupt 会抛出异常】

遇到中断

特殊情况,线程已经执行了 wait 方法,在此期间被中断了,会抛出 InterruptException 方法,并且释放掉目前已经获得的 Monitor


nofity() 会唤醒单个正在等待某对象 Monitor 对象的线程,唤醒的时候如果有多个线程在等待则会随机唤醒一个,具体的实现是交给 JVM 来说实现的。

wait()notify() 必须在 synchronized 代码块中执行,否则会抛出异常。

2.2 代码演示:4种情况

普通用法

 /**
 * 展示wait和notify的基本用法
 * 1. 研究代码执行顺序
 * 2. 证明wait释放锁
 */
 public class Wait {
     public static Object object = new Object();
  
     static class Thread1 extends Thread {
         @Override
         public void run() {
             // 获取同步监视器
             synchronized (object) {
                 System.out.println(Thread.currentThread().getName() + "开始执行了");
                 try {
                     // 释放锁
                     object.wait();
                     // 如果在等待期间遇到了中断,需要处理异常
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
                 // 重新获得了锁
                 System.out.println("线程" + Thread.currentThread().getName() + "获取到了锁。");
             }
         }
     }
  
     static class Thread2 extends Thread {
  
         @Override
         public void run() {
             synchronized (object) {
                 object.notify();
                 // 此时还没有释放锁,同步代码块里面所有语句执行完毕之后才会释放
                 System.out.println("线程" + Thread.currentThread().getName() + "调用了notify()");
             }
         }
     }
  
     public static void main(String[] args) throws InterruptedException {
         // 保证wait()先执行,notify()后执行
         Thread1 thread1 = new Thread1();
         Thread2 thread2 = new Thread2();
         thread1.start();
         Thread.sleep(200);
         thread2.start();
     }
 }

运行结果:

 Thread-0开始执行了
 线程Thread-1调用了notify()
 线程Thread-0获取到了锁。

notifynotifyAll 展示:

 /**
 * 3个线程,线程1和线程2首先被阻塞,线程3唤醒它们。notify, notifyAll。
 * start先执行不代表线程先启动。
 */
 public class WaitNotifyAll implements Runnable {
  
     public static final Object resourceA = new Object();
  
     public static void main(String[] args) throws InterruptedException {
         Runnable r = new WaitNotifyAll();
         Thread threadA = new Thread(r, "ThreadA");
         Thread threadB = new Thread(r, "ThreadB");
  
         Thread threadC = new Thread(new Runnable() {
             @Override
             public void run() {
                 synchronized (resourceA) {
                     resourceA.notifyAll();
                     System.out.println("ThreadC notified.");
                 }
             }
         });
  
         threadA.start();
         threadB.start();
         Thread.sleep(200);
         threadC.start();
     }
  
     @Override
     public void run() {
         synchronized (resourceA) {
             System.out.println(Thread.currentThread().getName() + " got resourceA lock.");
             try {
                 System.out.println(Thread.currentThread().getName() + " waits to start.");
                 resourceA.wait();
                 System.out.println(Thread.currentThread().getName() + "'s waiting to end.");
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
         }
     }
 }
  

输出如下:

 ThreadA got resourceA lock.
 ThreadA waits to start.
 ThreadB got resourceA lock.
 ThreadB waits to start.
 ThreadC notified.
 ThreadB's waiting to end.
 ThreadA's waiting to end.

如果使用 notify() 而不是 notifyAll()

 ThreadA got resourceA lock.
 ThreadA waits to start.
 ThreadB got resourceA lock.
 ThreadB waits to start.
 ThreadC notified.
 ThreadA's waiting to end.

可见只有 ThreadA 被唤醒了,并且程序永远不会结束。

如果注释掉 Thread.sleep(200);运行结果如下:

 ThreadA got resourceA lock.
 ThreadA waits to start.
 ThreadC notified.
 ThreadB got resourceA lock.
 ThreadB waits to start.
 ThreadA's waiting to end.

线程 C 唤醒了阻塞的线程 A ,但是之后拿到锁的线程 B 就无法获得锁了。

只释放当前 monitor 展示

执行 wait方法就是一个对象,哪一个对象执行 wait 就会释放这个对象对应的锁,而不会影响到其他的锁,锁与锁之间的行为是独立的。

 /**
 * 证明wait只释放当前的那把锁
 */
 public class WaitNotifyReleaseOwnMonitor {
     private static volatile Object resourceA = new Object();
     private static volatile Object resourceB = new Object();
  
     public static void main(String[] args) {
  
         // 验证thread1是否还持有resourceB锁
         Thread thread1 = new Thread(new Runnable() {
             @Override
             public void run() {
                 synchronized (resourceA) {
                     System.out.println("ThreadA got resourceA lock.");
                     synchronized (resourceB) {
                         System.out.println("ThreadA got resourceB lock.");
                         try {
                             System.out.println("ThreadA releases resourceA lock.");
                             resourceA.wait();
                         } catch (InterruptedException e) {
                             e.printStackTrace();
                         }
                     }
                 }
             }
         });
  
         Thread thread2 = new Thread(new Runnable() {
             @Override
             public void run() {
                 try {
                     Thread.sleep(1000);
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
                 synchronized (resourceA) {
                     System.out.println("ThreadB got resourceA lock.");
                     System.out.println("ThreadB tries to resourceB lock.");
                     synchronized (resourceB) {
                         System.out.println("ThreadB got resourceB lock.");
                     }
                 }
             }
         });
  
         thread1.start();
         thread2.start();
     }
 }

运行结果:

 ThreadA got resourceA lock.
 ThreadA got resourceB lock.
 ThreadA releases resourceA lock.
 ThreadB got resourceA lock.
 ThreadB tries to resourceB lock.

2.3 特点、性质

  • 用必须先拥有 monitor 也即 synchronized
  • 只能唤醒其中一个
  • 属于 Object 类【任何对象都可以调用这些方法】
  • 类似功能的 Condition
  • 同时持有多个锁的情况

2.4 原理

  • 入口集 Entry Set
  • 等待集 Wait Set

image-20210301174058933

2.5 注意点

Object.wait() 状态刚被唤醒时,通常不能立刻抢到 monitor 锁,那就会从 Waiting 先进入 Blocked 状态抢到锁后再转换到 Runnable 状态(官方文档)

线程被唤醒之后一般是拿不到锁的,即进入 Block 状态;(即由 Waiting 状态到 Block 状态)

image-20210301175732516

如果发生异常,可以直接跳到终止 Terminated 状态,不必再遵循路径,比如可以从 Waiting 直接到 Terminated

手写生产者消费者设计模式

为什么要使用生产者和消费者模式?

生产者和消费者解耦

image-20210301180146395

生产者向队列中添加,如果加满了则进入阻塞,如果生产了(队列不空)就可以通知消费者来取了。

消费者从队列中取出,如果取完了则进入阻塞,如果取出了(队列不满)就可以通知生产者消费了。

image-20210301180250804

代码:

 /**
 * 用wait/notify来实现生产者消费者模式
 */
 public class ProducerConsumerModel {
  
     public static void main(String[] args) {
         EventStorage eventStorage = new EventStorage();
         Producer producer = new Producer(eventStorage);
         Consumer consumer = new Consumer(eventStorage);
         new Thread(producer).start();
         new Thread(consumer).start();
     }
 }
  
 class Producer implements Runnable {
  
     private EventStorage storage;
  
     public Producer(EventStorage storage) {
         this.storage = storage;
     }
  
     @Override
     public void run() {
         for (int i = 0; i < 100; i++) {
             storage.put();
         }
     }
 }
  
 class Consumer implements Runnable {
  
     private EventStorage storage;
  
     public Consumer(EventStorage storage) {
         this.storage = storage;
     }
  
     @Override
     public void run() {
         for (int i = 0; i < 100; i++) {
             storage.take();
         }
     }
 }
  
 /**
 * 资源类
 */
 class EventStorage {
     private int maxSize;
     private LinkedList<Date> storage;
  
     public EventStorage() {
         this.maxSize = 10;
         this.storage = new LinkedList<>();
     }
  
     public synchronized void put() {
         // 如果满了就等待
         while(storage.size() == maxSize) {
             try {
                 wait();
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
         }
         // 否则没满就加入并通知
         storage.add(new Date());
         System.out.println("仓库里有了" + storage.size() + "个产品。");
         notify();
     }
  
     public synchronized void take() {
         while(storage.size() == 0) {
             try {
                 wait();
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
         }
         System.out.println("拿到了" + storage.poll() + ", 现在仓库还剩下" + storage.size());
         notify();
     }
 }

2.6 常见面试问题

两个线程交替打印 0~100 的奇偶数

偶线程:0
奇线程:1
偶线程:2

基本思路: synchronized 有很多的废操作。比如偶数线程先拿到锁,count++ = 1,继续执行 while 循环,偶数线程可能继续拿到锁,此时不满足 count & 1 == 0的条件,继续循环,可能会有很多不必要的执行,直到奇数线程拿到锁并执行 count++

 /**
 * 两个线程交替打印0~100的奇偶数,用synchronized关键字实现
 */
 public class WaitNotifyPrintOddEvenSyn {
     private static int count;
     private static final Object lock = new Object();
  
     // 新建2个线程
     // 第一个只处理偶数,第二个只处理奇数(用位运算)
     // 用synchronized来通信
     public static void main(String[] args) {
         new Thread(new Runnable() {
             @Override
             public void run() {
                 while(count < 100) {
                     synchronized (lock) {
                         // 使用位运算提高效率
                         if((count & 1) == 0) {
                             System.out.println(Thread.currentThread().getName() + ":" + count++);
                         }
                     }
                 }
             }
         }, "偶数").start();
  
         new Thread(new Runnable() {
             @Override
             public void run() {
                 while(count < 100) {
                     synchronized (lock) {
                         // 使用位运算提高效率
                         if((count & 1) == 1) {
                             System.out.println(Thread.currentThread().getName() + ":" + count++);
                         }
                     }
                 }
             }
         }, "奇数").start();
     }
 }

更好的方法:wait()/notify() 效率更高

 /**
 * 两个线程交替打印0~100的奇偶数,用wait/notify实现
 */
 public class WaitNotifyPrintOddEvenWait {
     private static int count = 0;
     private static final Object lock = new Object();
  
     public static void main(String[] args) {
         new Thread(new TurningRunner(), "偶数").start();
         // 让偶数线程先与奇数线程启动
         try {
             Thread.sleep(10);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
         new Thread(new TurningRunner(), "奇数").start();
     }
  
     // 1. 拿到锁,我们就打印
     // 2. 打印完,唤醒其他线程,自己就休眠
     static class TurningRunner implements Runnable {
         @Override
         public void run() {
             while(count <= 100) {
                 synchronized (lock) {
                     // 拿到锁就打印
                     System.out.println(Thread.currentThread().getName() + ":" + count++);
                     // 唤醒其他线程
                     lock.notify();
                     if(count <= 100) {
                         try {
                             // 如果任务还没结束,就让出当前的锁,并休眠
                             lock.wait();
                         } catch (InterruptedException e) {
                             e.printStackTrace();
                         }
                     }
                 }
             }
         }
     }
 }

用程序实现两个线程交替打印 0~100 的奇偶数

手写生产者消费者设计模式

为什么 wait() 需要在同步代码块内使用,而 sleep() 不需要

为了安全,主要是让通信变得可靠,防止死锁或者永久等待的发生。因为如果不把 wait()notify() 都放到代码块里面的话,可能会导致先执行 notify() 再执行 wait() 。需要相互配合的操作都放到同步代码块里面了。

为什么线程通信的方法 wait()notify()notifyAll()被定义在 Object 类里?而 sleep 定义在 Thread类里?

锁级别的操作,锁是绑定到对象中,不是绑定在线程中。Java 对象设计的是每个对象都是一把锁。

wait 方法是 Object 属于对象的,那调用 Thread.wait 会怎么样?

Thread 也是一个对象,可以做,线程退出的时候会自动执行notify,会使得整个流程被影响,不推荐这样写

如何选择用 notify 还是 nofityAll

考虑是需要唤醒多个线程还是一个线程

notifyAll 之后所有的线程都会再次抢夺锁,如果某线程抢夺失败怎么办?

回到初始状态,陷入等待状态,等待持有者释放,直到拿到锁继续下一步操作

suspendresume 来阻塞线程可以吗?为什么?

因为安全问题已经被弃用了,推荐使用 waitntotify 来实现。

彩蛋:Java相关概念

JavaSEJavaEEJavaME 是什么?

SE是标准版,EE是企业版,ME是移动版,现在都是SE

JREJDKJVM 是什么关系?

JRE是Java运行时环境,是JDK的一部分;JDK是开发工具包,用于开发;JVM是JRE的一部分,JRE还有一些其他的类库

Java 版本升级都包括了哪些东西的升级?

类的升级以及JVM的升级

Java8Java1.8JDK8 是什么关系,是同一个东西吗?

最开始版本是1.1、1.2、1.3…,Java5。Java 8就是Java SE8。一般认为是同一个东西。

3.sleep方法详解

作用:我只想让线程在预期的时间执行,其他时候不要占用 CPU 资源

不释放锁,包括 synchronizedlock ,和 wait 不同

 /**
 * 展示线程sleep的时候不释放synchronized的monitor,等sleep时间到了以后,正常结束后才释放锁
 */
 public class SleepDontReleaseMonitor implements Runnable {
  
     public static void main(String[] args) {
         SleepDontReleaseMonitor sleepDontReleaseMonitor = new SleepDontReleaseMonitor();
         new Thread(sleepDontReleaseMonitor).start();
         new Thread(sleepDontReleaseMonitor).start();
     }
  
     @Override
     public void run() {
         syn();
     }
  
     private synchronized void syn() {
         System.out.println("线程" + Thread.currentThread().getName() + "获取到了monitor。");
         try {
             Thread.sleep(5000);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
         System.out.println("线程" + Thread.currentThread().getName() + "退出了同步代码块");
     }
 }

运行结果:

线程Thread-0获取到了monitor。
线程Thread-0退出了同步代码块
线程Thread-1获取到了monitor。
线程Thread-1退出了同步代码块
/**
* 演示sleep不释放lock(lock需要手动释放)
*/
public class SleepDontReleaseLock implements Runnable {
 
    private static final Lock lock = new ReentrantLock();
 
    @Override
    public void run() {
        lock.lock();
        System.out.println("线程" + Thread.currentThread().getName() + "获取到了锁");
        try {
            Thread.sleep(5000);
            System.out.println("线程" + Thread.currentThread().getName() + "已经苏醒");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
 
    public static void main(String[] args) {
        SleepDontReleaseLock sleepDontReleaseLock = new SleepDontReleaseLock();
        new Thread(sleepDontReleaseLock).start();
        new Thread(sleepDontReleaseLock).start();
    }
}

运行结果:

线程Thread-0获取到了锁
线程Thread-0已经苏醒
线程Thread-1获取到了锁
线程Thread-1已经苏醒

sleep 方法响应中断

  1. 抛出 InterruptedException
  2. 清除中断状态

第二种写法(更优雅)

/**
* 每个1秒钟输出当前时间,被中断,观察。
* Thread.sleep()
* TimeUnit.SECONDS.sleep()
*/
public class SleepInterrupted implements Runnable {
 
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new SleepInterrupted());
        thread.start();
        Thread.sleep(6500);
        thread.interrupt();
    }
 
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(new Date());
            try {
                // 1. 不需要转化时间
                // TimeUnit.HOURS.sleep()
                // 2. 如果传参小于0,忽略处理
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                System.out.println("我被中断了!");
                e.printStackTrace();
            }
        }
    }
}

运行结果:

Mon Mar 01 20:31:54 CST 2021
Mon Mar 01 20:31:55 CST 2021
Mon Mar 01 20:31:56 CST 2021
Mon Mar 01 20:31:57 CST 2021
Mon Mar 01 20:31:58 CST 2021
Mon Mar 01 20:31:59 CST 2021
Mon Mar 01 20:32:00 CST 2021
我被中断了!
Mon Mar 01 20:32:01 CST 2021
java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at java.lang.Thread.sleep(Thread.java:340)
    at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
    at threadcoreknowledge.threadobjectclasscommonmethods.SleepInterrupted.run(SleepInterrupted.java:27)
    at java.lang.Thread.run(Thread.java:748)
Mon Mar 01 20:32:02 CST 2021
Mon Mar 01 20:32:03 CST 2021

一句话总结:sleep 方法可以让线程进入 Waiting 状态,并且不占用 CPU 资源,但是不释放锁,直到规定时间后再执行,休眠期间如果被中断,会抛出异常并清除中断状态

wait/notify、sleep 异同(方法属于哪个对象?线程状态怎么切换?)

相同:①都会让线程进入阻塞状态;②可以响应中断

不同:①前者必须在同步方法中执行,后者不需要;②前者释放锁,后者不释放锁;③前者不指定时间,等待被唤醒。后者指定时间,到时间就会被唤醒;④前者在 Object 类中,后者在 Thread 类中。

4.join方法

4.1 作用

作用:因为新的线程加入了我们,所以我们要等他执行完再出发

用法:main 等待 thread 执行完毕,注意谁等谁

4.2 用法

4.3 三个例子

 /**
 * 演示join,注意语句输出顺序,会变化。
 */
 public class Join {
     public static void main(String[] args) throws InterruptedException {
         Thread thread1 = new Thread(new Runnable() {
             @Override
             public void run() {
                 try {
                     Thread.sleep(1000);
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
                 System.out.println(Thread.currentThread().getName() + "执行完毕");
             }
         });
  
         Thread thread2 = new Thread(new Runnable() {
             @Override
             public void run() {
                 try {
                     Thread.sleep(1000);
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
                 System.out.println(Thread.currentThread().getName() + "执行完毕");
             }
         });
  
         thread1.start();
         thread2.start();
         System.out.println("开始等待子线程运行完毕");
         thread1.join();
         thread2.join();
         // 由于join的生效,因此线程的输出语句一定在下面这条语句之前完成
         System.out.println("所有子线程执行完毕");
     }
 }

运行结果:

 开始等待子线程运行完毕
 Thread-1执行完毕
 Thread-0执行完毕
 所有子线程执行完毕

如果注释掉 join 语句

运行结果:

 开始等待子线程运行完毕
 所有子线程执行完毕
 Thread-1执行完毕
 Thread-0执行完毕

关于中断的情况:

 /**
 * 演示join期间被中断的效果
 */
 public class JoinInterrupt {
     public static void main(String[] args) {
         // 获取主线程的引用
         Thread mainThread = Thread.currentThread();
  
         Thread thread = new Thread(new Runnable() {
             @Override
             public void run() {
                 try {
                     // 中断主线程,此时主线程已经给子线程让位了
                     mainThread.interrupt();
                     Thread.sleep(5000);
                     System.out.println("Thread1 finished");
                 } catch (InterruptedException e) {
                     System.out.println("子线程中断");
                     // e.printStackTrace();
                 }
             }
         });
         // 启动子线程
         thread.start();
         System.out.println("等待子线程运行完毕");
         // 子线程来插队了,实际上是主线程被中断,主线程抛出异常
         try {
             thread.join();
         } catch (InterruptedException e) {
             System.out.println(Thread.currentThread().getName() + "主线程中断了");
             // 主线程被中断的时候必须还要中断子线程,否则会出现不一致的情况
             thread.interrupt();
             // e.printStackTrace();
         }
         System.out.println("子线程已经运行完毕");
     }
 }
 等待子线程运行完毕
 main主线程中断了
 子线程已经运行完毕
 子线程中断

获取状态:

 /**
 * 先join再mainThread.getState(),通过debugger看线程join前后状态的对比
 */
 public class JoinThreadState {
     public static void main(String[] args) throws InterruptedException {
         Thread mainThread = Thread.currentThread();
  
         Thread thread = new Thread(new Runnable() {
             @Override
             public void run() {
                 try {
                     Thread.sleep(3000);
                     System.out.println(mainThread.getState());
                     System.out.println("Thread-0运行结束");
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
             }
         });
         thread.start();
         System.out.println("等待子线程运行完毕");
         thread.join();
         System.out.println("子线程运行完毕");
     }
 }

运行结果:

 等待子线程运行完毕
 WAITING
 Thread-0运行结束
 子线程运行完毕

image-20221109012511347

4.4 CountDownLatchCyclicBarrier

尽量不要直接操作底层的方法,使用更高级的封装好的类库。

4.5原理

源码

 public final void join() throws InterruptedException {
     // 休眠时间无限
     join(0);
 }
 public final synchronized void join(long millis)
     throws InterruptedException {
     long base = System.currentTimeMillis();
     long now = 0;
  
     if (millis < 0) {
         throw new IllegalArgumentException("timeout value is negative");
     }
  
     if (millis == 0) {
         while (isAlive()) {
             // 一直休眠
             wait(0);
         }
     } else {
         while (isAlive()) {
             long delay = millis - now;
             if (delay <= 0) {
                 break;
             }
             wait(delay);
             now = System.currentTimeMillis() - base;
         }
     }
 }

这里只有 wait(0),那么究竟是被谁唤醒的呢?

分析

image-20210301212110867

等价

 /**
 * 通过讲解join原理,分析出join的代替写法
 */
 public class JoinPrinciple {
     public static void main(String[] args) throws InterruptedException {
         Thread thread = new Thread(new Runnable() {
             @Override
             public void run() {
                 try {
                     Thread.sleep(1000);
                     System.out.println("Thread0 finished");
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
             }
         });
         thread.start();
         System.out.println("开始等待子线程运行完毕");
         // thread.join();
         // 主线程拿到锁
         synchronized (thread) {
             thread.wait();
         }
         System.out.println("所有子线程执行完毕");
     }
 }
 开始等待子线程运行完毕
 Thread0 finished
 所有子线程执行完毕

4.6常见面试问题

join 期间,线程处于哪种线程状态?

线程会处于 WAITING 的状态

5.yield方法

作用:释放我的 CPU 时间片 –> Runnable状态,有可能下一段又被执行了

定位:JVM 不保证遵循

yieldsleep区别:是否随时可能再次被调度

6.获取当前执行线程的引用: Thread.currentThread 方法

 /**
 * 演示打印main,Thread-0,Thread-1
 */
 public class CurrentThread implements Runnable {
  
     public static void main(String[] args) {
         new CurrentThread().run();
         new Thread(new CurrentThread()).start();
         new Thread(new CurrentThread()).start();
     }
  
     @Override
     public void run() {
         System.out.println(Thread.currentThread().getName());
     }
 }