Java 5中引入了新的锁机制——java.util.concurrent.locks中的显式的互斥锁:Lock接口,它提供了比synchronized更加广泛的锁定操作。Lock接口有3个实现它的类:ReentrantLock、ReetrantReadWriteLock.ReadLock和ReetrantReadWriteLock.WriteLock,即重入锁、读锁和写锁。

基本语法

跟synchronized 不一样的是,synchronized 是关键字级别来保护临界区,而ReentrantLock是在对象级别来保护临界区;
lock必须被显式地创建、锁定和释放,为了可以使用更多的功能,一般用ReentrantLock为其实例化。
为了保证锁最终一定会被释放(可能会有异常发生),要把互斥区(临界区)放在try语句块内,并在finally语句块中释放锁,尤其当有return语句时,return语句必须放在try字句中,以确保unlock()不会过早发生(执行),从而将数据暴露给第二个任务。
所以要先去创建一个ReentrantLock对象,然后调用该对象的lock方法去获取锁。然后就是try跟finally块,其中try中的代码就是临界区中的代码。finally就是确保将来是否出现异常都将锁释放掉。

  1. //获取ReentrantLock对象,默认使用非公平锁,如果要使用公平锁,需要传入参数true
  2. private ReentrantLock lock = new ReentrantLock();
  3. //加锁
  4. lock.lock();
  5. try {
  6. //更新对象的状态
  7. //捕获异常,必要时恢复到原来的不变约束
  8. //如果有return语句,放在这里
  9. }finally {
  10. //释放锁,锁必须在finally块中释放
  11. lock.unlock();
  12. }

ReetrankLock与synchronized比较

性能方面

在JDK1.5中,synchronized是性能低效的。因为这是一个重量级操作,它对性能最大的影响是阻塞的是实现,挂起线程和恢复线程的操作都需要转入内核态中完成,这些操作给系统的并发性带来了很大的压力。

但是到了JDK1.6之后,就发生了变化,对synchronize加入了很多优化措施,有自适应自旋,锁消除,锁粗化,轻量级锁,偏向锁等等。导致在JDK1.6之后的synchronize性能并不比Lock差。

浅析两种锁机制的底层的实现策略 (乐观锁与悲观锁的体现)

互斥同步最主要的问题就是进行线程阻塞和唤醒所带来的性能问题,因而这种同步又称为阻塞同步,它属于一种悲观的并发策略,即线程获得的是独占锁。独占锁意味着其他线程只能依靠阻塞来等待线程释放锁。而在CPU转换线程阻塞时会引起线程上下文切换,当有很多线程竞争锁的时候,会引起CPU频繁的上下文切换导致效率很低。synchronized采用的便是这种并发策略。

随着指令集的发展,我们有了另一种选择:基于冲突检测的乐观并发策略,通俗地讲就是先进性操作,如果没有其他线程争用共享数据,那操作就成功了,如果共享数据被争用,产生了冲突,那就再进行其他的补偿措施(最常见的补偿措施就是不断地重拾,直到试成功为止),这种乐观的并发策略的许多实现都不需要把线程挂起,因此这种同步被称为非阻塞同步。ReetrantLock采用的便是这种并发策略(ReetrantLock是独占式的悲观锁,但底层用到了AQS思想,AQS用到了CAS思想)。

在乐观的并发策略中,需要操作和冲突检测这两个步骤具备原子性,它靠硬件指令来保证,这里用的是CAS操作(Compare and Swap)。JDK1.5之后,Java程序才可以使用CAS操作。我们可以进一步研究ReentrantLock的源代码,会发现其中比较重要的获得锁的一个方法是compareAndSetState,这里其实就是调用的CPU提供的特殊指令。现代的CPU提供了指令,可以自动更新共享数据,而且能够检测到其他线程的干扰,而compareAndSet() 就用这些代替了锁定。这个算法称作非阻塞算法,意思是一个线程的失败或者挂起不应该影响其他线程的失败或挂起。

用途比较

  1. 基本语法上,ReentrantLocksynchronized很相似,**它们都具备一样的线程重入特性**,只是代码写法上有点区别而已,一个表现为API层面的互斥锁(Lock),一个表现为原生语法层面的互斥锁(synchronized)。ReentrantLock相对synchronized而言还是增加了一些高级功能,主要有以下三项:

1、等待可中断:当持有锁的线程长期不释放锁时,正在等待锁的线程可以选择放弃等待,改为处理其他事情,它对处理执行时间非常上的同步块很有帮助。而在等待由synchronized产生的互斥锁时,会一直阻塞,是不能被中断的。

2、可实现公平锁:多个线程在等待同一个锁时,必须按照申请锁的时间顺序排队等待,而非公平锁则不保证这点,在锁释放时,任何一个等待锁的线程都有机会获得锁。synchronized中的锁时非公平锁,ReentrantLock默认情况下也是非公平锁,但可以通过构造方法ReentrantLock(ture)来要求使用公平锁。

3、锁可以绑定多个条件:ReentrantLock对象可以同时绑定多个Condition对象(名曰:条件变量或条件队列),而在synchronized中,锁对象的wait()和notify()或notifyAll()方法可以实现一个隐含条件,但如果要和多于一个的条件关联的时候,就不得不额外地添加一个锁,而ReentrantLock则无需这么做,只需要多次调用newCondition()方法即可。而且我们还可以通过绑定Condition对象来判断当前线程通知的是哪些线程(即与Condition对象绑定在一起的其他线程)。

ReentrantLock特点

1、支持锁重入

  • 可重入锁是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此 有权利再次获取这把锁
  • 如果是不可重入锁,那么在尝试第二次获得锁时,自己也会被锁挡住

示例:
跟之前用synchronized 关键不一样,synchronized 关键字这种方式是将对象当成锁,当然真正的锁是对象关联的Monitor**;**

但是现在我们创建出来的ReentrantLock对象**本身即是锁**,将来线程执行 lock.lock();的时候,该线程就是这个锁对象的主人。若果获取不了锁,即成为主人失败就进入到这个lock内部的等待队列去等待。

  1. public class ReentrantLockMainTest {
  2. private static ReentrantLock lock = new ReentrantLock();
  3. public static void main(String[] args) {
  4. lock.lock();//尝试获取锁,获取成功则继续往后执行, 如果有竞争就进入`阻塞队列`, 一直等待着,不能被打断
  5. try {
  6. System.out.println("lock...");
  7. m1();
  8. }finally {
  9. lock.unlock();
  10. }
  11. }
  12. public static void m1(){
  13. System.out.println("是否可重入...");
  14. lock.lock(); //
  15. try {
  16. System.out.println("能进来,说明可重入...");
  17. System.out.println("是否支持多次重入...");
  18. m2();
  19. }finally {
  20. lock.unlock();
  21. }
  22. }
  23. public static void m2(){
  24. lock.lock(); //
  25. try {
  26. System.out.println("能进来,说明支持多次重入......");
  27. }finally {
  28. lock.unlock();
  29. }
  30. }
  31. }

image.png
如果是不可重入的话,在尝试第二次获取锁的时候就会被阻塞住了;

2、可中断

(针对于lockInterruptibly()方法获得的中断锁) 可中断意味着直接退出阻塞队列, 获取锁失败

  • 对于synchronized 和 reentrantlock.lock() 的锁, 两个都是不可被打断的; 也就是说别的线程已经获得了锁, 我的线程就需要一直等待下去, 不能中断,直到我们的线程获取到锁对象。
  • 可被中断的锁是指通过lock.lockInterruptibly()获取的锁对象, 可以通过调用阻塞线程的interrupt()方法去中断
  • 如果某个线程处于阻塞状态,可以调用其interrupt方法让其停止阻塞,获得锁失败
    • 处于阻塞状态的线程,被打断了就不用阻塞了,**直接停止运行**
  • 可中断的锁, 在一定程度上可以被动的减少死锁的概率, 之所以被动, 是因为我们需要手动调用阻塞线程的interrupt方法;

测试使用lock.lockInterruptibly()获得的锁可以从阻塞队列中打断

  1. /**
  2. * Description: ReentrantLock, 演示RenntrantLock中的可打断锁方法 lock.lockInterruptibly();
  3. */
  4. public class ReentrantLockMainTest {
  5. private static final ReentrantLock lock = new ReentrantLock();
  6. public static void main(String[] args) throws InterruptedException {
  7. Thread t1=new Thread(()->{
  8. //获取一个可打断的锁
  9. try {
  10. lock.lockInterruptibly();
  11. } catch (InterruptedException e) {
  12. e.printStackTrace();
  13. System.out.println("t1线程在等待锁的过程中被打断....");
  14. return; //打断获取锁后不应该继续往后执行
  15. }
  16. try {
  17. System.out.println("t1线程获取到锁.....");
  18. }finally {
  19. lock.unlock();
  20. }
  21. },"t1");
  22. lock.lock(); //让主线程获取到锁..
  23. t1.start();//启动t1线程
  24. //让主线程睡一会儿,期间主线程拿到锁不释放,t1只好等待,这个时候打断t1线程不让它继续等下去
  25. try {
  26. Thread.sleep(2000);
  27. t1.interrupt();//
  28. System.out.println("打断线程....");
  29. }finally {
  30. lock.unlock();
  31. }
  32. }
  33. }

image.png


3、锁超时

(lock.tryLock())方法 超时后直接退出阻塞队列, 获取锁失败。 防止无限制等待, 减少死锁的发生

  • 使用 lock.tryLock() 方法会立刻返回获取锁是否成功。如果成功则返回true,反之则返回false。
  • 并且tryLock方法可以设置指定等待时间,参数为:tryLock(long timeout, TimeUnit unit) , 其中timeout为最长等待时间,TimeUnit为时间单位
  • 获取锁的过程中, 如果超过等待时间, 或者被打断, 就直接从阻塞队列移除, 此时获取锁就失败了, 不会一直阻塞着 ! (可以用来解决死锁问题)
  • 不设置等待时间, 立即失败 ```java /**

    • Description: ReentrantLock, 演示RenntrantLock中的tryLock(), 获取锁立即失败

    */ @Slf4j(topic = “guizy.ReentrantTest”) public class ReentrantTest {

    private static final ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {

    1. Thread t1 = new Thread(() -> {
    2. log.debug("尝试获得锁");
    3. // 此时肯定获取失败, 因为主线程已经获得了锁对象
    4. if (!lock.tryLock()) {
    5. log.debug("获取立刻失败,返回");
    6. return;
    7. }
    8. try {
    9. log.debug("获得到锁");
    10. } finally {
    11. lock.unlock();
    12. }
    13. }, "t1");
    14. lock.lock();
    15. log.debug("获得到锁");
    16. t1.start();
    17. // 主线程2s之后才释放锁
    18. Sleeper.sleep(2);
    19. log.debug("释放了锁");
    20. lock.unlock();

    } }

  1. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/28814483/1652875241684-78868d90-ed3a-4de7-8ff4-d54b7aded114.png#clientId=u3a40fb5c-dc33-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=125&id=u74e5d2bd&margin=%5Bobject%20Object%5D&name=image.png&originHeight=156&originWidth=873&originalType=binary&ratio=1&rotation=0&showTitle=false&size=61529&status=done&style=none&taskId=u09181191-66e0-42a5-af3a-d35aba9b453&title=&width=698.4)
  2. **设置等待时间, 超过等待时间还没有获得锁, 失败, 从阻塞队列移除该线程**
  3. ```java
  4. /**
  5. * Description: ReentrantLock, 演示RenntrantLock中的tryLock(long mills), 超过锁设置的等待时间,就从阻塞队列移除
  6. */
  7. @Slf4j(topic = "guizy.ReentrantTest")
  8. public class ReentrantTest {
  9. private static final ReentrantLock lock = new ReentrantLock();
  10. public static void main(String[] args) {
  11. Thread t1 = new Thread(() -> {
  12. log.debug("尝试获得锁");
  13. try {
  14. // 设置等待时间, 超过等待时间 / 被打断, 都会获取锁失败; 退出阻塞队列
  15. if (!lock.tryLock(1, TimeUnit.SECONDS)) {
  16. log.debug("获取锁超时,返回");
  17. return;
  18. }
  19. } catch (InterruptedException e) {
  20. log.debug("被打断了, 获取锁失败, 返回");
  21. e.printStackTrace();
  22. return;
  23. }
  24. try {
  25. log.debug("获得到锁");
  26. } finally {
  27. lock.unlock();
  28. }
  29. }, "t1");
  30. lock.lock();
  31. log.debug("获得到锁");
  32. t1.start();
  33. // t1.interrupt();
  34. // 主线程2s之后才释放锁
  35. Sleeper.sleep(2);
  36. log.debug("main线程释放了锁");
  37. lock.unlock();
  38. }
  39. }

image.png

4、通过lock.tryLock()来解决, 哲学家就餐问题 (重点)

  1. /**
  2. * Description:
  3. 使用了ReentrantLock锁, 该类中有一个tryLock()方法, 在指定时间内获取不到锁对
  4. 象, 就从阻塞队列移除,不用一直等待。当获取了左手边的筷子之后, 尝试获取右手边的筷子,
  5. 如果该筷子被其他哲学家占用, 获取失败, 此时就先把自己左手边的筷子,给释放掉.
  6. 这样就避免了死锁问题
  7. */
  8. @Slf4j(topic = "guizy.PhilosopherEat")
  9. public class PhilosopherEat {
  10. public static void main(String[] args) {
  11. Chopstick c1 = new Chopstick("1");
  12. Chopstick c2 = new Chopstick("2");
  13. Chopstick c3 = new Chopstick("3");
  14. Chopstick c4 = new Chopstick("4");
  15. Chopstick c5 = new Chopstick("5");
  16. new Philosopher("苏格拉底", c1, c2).start();
  17. new Philosopher("柏拉图", c2, c3).start();
  18. new Philosopher("亚里士多德", c3, c4).start();
  19. new Philosopher("赫拉克利特", c4, c5).start();
  20. new Philosopher("阿基米德", c5, c1).start();
  21. }
  22. }
  23. @Slf4j(topic = "guizy.Philosopher")
  24. class Philosopher extends Thread {
  25. final Chopstick left;
  26. final Chopstick right;
  27. public Philosopher(String name, Chopstick left, Chopstick right) {
  28. super(name);
  29. this.left = left;
  30. this.right = right;
  31. }
  32. @Override
  33. public void run() {
  34. while (true) {
  35. // 获得了左手边筷子 (针对五个哲学家, 它们刚开始肯定都可获得左筷子)
  36. if (left.tryLock()) {
  37. try {
  38. // 此时发现它的right筷子被占用了, 使用tryLock(),
  39. // 尝试获取失败, 此时它就会将自己左筷子也释放掉
  40. // 临界区代码
  41. if (right.tryLock()) { //尝试获取右手边筷子, 如果获取失败, 则会释放左边的筷子
  42. try {
  43. eat();
  44. } finally {
  45. right.unlock();
  46. }
  47. }
  48. } finally {
  49. left.unlock();
  50. }
  51. }
  52. }
  53. }
  54. private void eat() {
  55. log.debug("eating...");
  56. Sleeper.sleep(0.5);
  57. }
  58. }
  59. // 继承ReentrantLock, 让筷子类称为锁
  60. class Chopstick extends ReentrantLock {
  61. String name;
  62. public Chopstick(String name) {
  63. this.name = name;
  64. }
  65. @Override
  66. public String toString() {
  67. return "筷子{" + name + '}';
  68. }
  69. }

公平锁 new ReentrantLock(true)

  • ReentrantLock默认是非公平锁, 可以指定为公平锁。

    1. //默认是不公平锁,需要在创建时指定为公平锁
    2. ReentrantLock lock = new ReentrantLock(true);
  • 在线程获取锁失败,进入阻塞队列时,先进入的会在锁被释放后先获得锁。这样的获取方式就是公平的。一般不设置ReentrantLock为公平的, 会降低并发度。

  • Synchronized底层的Monitor锁就是不公平的, 和谁先进入阻塞队列是没有关系的。

公平锁 (new ReentrantLock(true))
  • 公平锁, 可以把竞争的线程放在一个先进先出的阻塞队列上。
  • 只要持有锁的线程执行完了, 唤醒阻塞队列中的下一个线程获取锁即可; 此时先进入阻塞队列的线程先获取到锁

非公平锁 (synchronized, new ReentrantLock())
  • 非公平锁, 当阻塞队列中已经有等待的线程A了, 此时后到的线程B, 先去尝试看能否获得到锁对象. 如果获取成功, 此时就不需要进入阻塞队列了. 这样以来后来的线程B就先获得锁了

所以公平和非公平的区别 : 线程执行同步代码块时, 是否会去尝试获取锁, 如果会尝试获取锁, 那就是非公平的, 如果不会尝试获取锁, 直接进入阻塞队列, 再等待被唤醒, 那就是公平的

  • 如果不进入队列呢? 线程一直尝试获取锁不就行了?
    • 一直尝试获取锁, 是在synchronized轻量级锁升级为重量级锁时, 做的一个优化, 叫做自旋锁, 一般很消耗资源, cpu一直空转, 最后获取锁也失败, 所以不推荐使用。在jdk6对于自旋锁有一个机制, 在重试获得锁指定次数就失败等等 。

条件变量 (可避免虚假唤醒)

lock.newCondition()创建条件变量对象; 通过条件变量对象调用**await/signal**方法, 等待/唤醒

  • Synchronized 中也有条件变量,当条件不满足时进入Monitor监视器中的 waitSet等待集合
  • ReentrantLock 的条件变量比 synchronized 强大之处在于,它是 支持多个条件变量。这就好比synchronized 是那些不满足条件的线程都在一间休息室等通知; (此时会造成虚假唤醒), 而 ReentrantLock 支持多间休息室,有专门等烟的休息室、专门等早餐的休息室、唤醒时也是按休息室来唤醒; (可以避免虚假唤醒)

使用要点:

  • await 前需要 获得锁
  • await 执行后,会释放锁,进入 conditionObject (条件变量)中等待
  • await 的线程被唤醒(或打断、或超时)取重新竞争 lock 锁
  • 竞争 lock 锁成功后,从 await 后继续执行
  • signal 方法用来唤醒条件变量(等待室)汇总的某一个等待的线程
  • signalAll方法, 唤醒条件变量(休息室)中的所有线程

在之前的案例中,我们用synchronized实现互斥,并配合使用Object对象的wait()和notify()或notifyAll()方法来实现线程间协作。
Java 5之后,我们可以用Reentrantlock锁配合Condition对象上的await()和signal()或signalAll()方法来实现线程间协作。在ReentrantLock对象上newCondition()可以得到一个Condition对象,可以通过在Condition上调用await()方法来挂起一个任务(线程),通过在Condition上调用signal()来通知任务,从而唤醒一个任务,或者调用signalAll()来唤醒所有在这个Condition上被其自身挂起的任务。另外,如果使用了公平锁,signalAll()的与Condition关联的所有任务将以FIFO队列的形式获取锁,如果没有使用公平锁,则获取锁的任务是随机的,这样我们便可以更好地控制处在await状态的任务获取锁的顺序。与notifyAll()相比,signalAll()是更安全的方式。另外,它可以指定唤醒与自身Condition对象绑定在一起的任务。

  1. /**
  2. * Description: ReentrantLock可以设置多个条件变量(多个休息室), 相对于synchronized底层monitor锁中waitSet
  3. */
  4. public class ConditionVariable {
  5. private static boolean hasCigarette = false;
  6. private static boolean hasTakeout = false;
  7. private static final ReentrantLock lock = new ReentrantLock();
  8. static Condition waitCigaretteSet = lock.newCondition();
  9. static Condition waitTakeoutSet = lock.newCondition();
  10. public static void main(String[] args) throws InterruptedException {
  11. new Thread(()->{
  12. //尝试获取锁
  13. lock.lock();
  14. try {
  15. System.out.println("获取到锁,如果能获取到需要的资源--烟,就可以继续往下执行,没有获取到资源则一直循环尝试获取...");
  16. while (!hasCigarette){
  17. System.out.println("没获取到需要的资源..烟,此时进行等待..");
  18. try {
  19. waitCigaretteSet.await(); //进行等待
  20. } catch (InterruptedException e) {
  21. e.printStackTrace();
  22. }
  23. }
  24. System.out.println("等到当前线程需要的资源--烟了,开始干活");
  25. }finally {
  26. lock.unlock();
  27. }
  28. },"小南").start();
  29. new Thread(()->{
  30. //尝试获取锁
  31. lock.lock();
  32. try {
  33. System.out.println("获取到锁,如果能获取到需要的资源--外卖,就可以继续往下执行,没有获取到资源则一直循环尝试获取...");
  34. while (!hasTakeout){
  35. System.out.println("没获取到需要的资源..外卖,此时进行等待..");
  36. try {
  37. waitTakeoutSet.await(); //进行等待
  38. } catch (InterruptedException e) {
  39. e.printStackTrace();
  40. }
  41. }
  42. System.out.println("等到当前线程需要的资源--外卖l ,开始干活");
  43. }finally {
  44. lock.unlock();
  45. }
  46. },"小女").start();
  47. Thread.sleep(1000);
  48. new Thread(()->{
  49. lock.lock();
  50. try {
  51. System.out.println("我是来送烟的...");
  52. hasCigarette=true;
  53. waitCigaretteSet.signal();
  54. }finally {
  55. lock.unlock();
  56. }
  57. },"送烟的").start();
  58. Thread.sleep(1000);
  59. new Thread(()->{
  60. lock.lock();
  61. try {
  62. System.out.println("我是来送外卖的...");
  63. hasTakeout=true;
  64. waitTakeoutSet.signal();
  65. }finally {
  66. lock.unlock();
  67. }
  68. },"送外卖的").start();
  69. }
  70. }


同步模式之顺序控制

  • 假如有两个线程, 线程A打印1, 线程B打印2.
  • 要求: 程序先打印2, 再打印1
    用wait/ notify实现

使用synchronized+锁对象的wait/ notify方法进行线程之间的协调

  1. public class FirstMainTest {
  2. /*
  3. - 假如有两个线程, 线程A打印1, 线程B打印2.
  4. - 要求: 程序先打印2, 再打印1
  5. * */
  6. private static Object room=new Object();
  7. private static boolean P1=false;
  8. private static boolean P2=true;
  9. //用wait/ notify实现
  10. public static void main(String[] args) {
  11. new Thread(()->{
  12. synchronized (room){
  13. while (!P1){
  14. try {
  15. room.wait();
  16. } catch (InterruptedException e) {
  17. e.printStackTrace();
  18. }
  19. }
  20. if (P1){
  21. System.out.println("A1");
  22. }
  23. }
  24. },"A").start();
  25. new Thread(()->{
  26. synchronized (room){
  27. while (!P2){
  28. try {
  29. room.wait();
  30. } catch (InterruptedException e) {
  31. e.printStackTrace();
  32. }
  33. }
  34. if (P2){
  35. System.out.println("B2");
  36. room.notifyAll();
  37. P1=true;
  38. }
  39. }
  40. },"B").start();
  41. }
  42. }

使用await() / signal 方法实现
  1. public class FirstMainTest {
  2. /*
  3. - 假如有两个线程, 线程A打印1, 线程B打印2.
  4. - 要求: 程序先打印2, 再打印1
  5. * */
  6. private static ReentrantLock lock=new ReentrantLock();
  7. private static boolean P1=false;
  8. private static boolean P2=true;
  9. static Condition waitP1Set = lock.newCondition();
  10. static Condition waitP2Set = lock.newCondition();
  11. public static void main(String[] args) {
  12. new Thread(()->{
  13. lock.lock();
  14. try {
  15. while (!P1){
  16. try {
  17. waitP1Set.await();
  18. } catch (InterruptedException e) {
  19. e.printStackTrace();
  20. }
  21. }
  22. if (P1){
  23. System.out.println("A1");
  24. }
  25. }finally {
  26. lock.unlock();
  27. }
  28. },"A").start();
  29. new Thread(()->{
  30. lock.lock();
  31. try {
  32. while (!P2){
  33. try {
  34. waitP2Set.await();
  35. } catch (InterruptedException e) {
  36. e.printStackTrace();
  37. }
  38. }
  39. if (P2){
  40. System.out.println("B2");
  41. P1=true;
  42. waitP1Set.signal();
  43. }
  44. }finally {
  45. lock.unlock();
  46. }
  47. },"B").start();
  48. }
  49. }

使用LockSupport中的part与unpart实现
  1. public class FirstMainTest {
  2. /*
  3. - 假如有两个线程, 线程A打印1, 线程B打印2.
  4. - 要求: 程序先打印2, 再打印1
  5. * */
  6. private static boolean P1=false;
  7. private static boolean P2=true;
  8. public static void main(String[] args) {
  9. Thread t1 = new Thread(()->{
  10. while (!P1){
  11. LockSupport.park();
  12. }
  13. if (P1){
  14. System.out.println("A1");
  15. }
  16. },"A");
  17. t1.start();
  18. Thread t2 = new Thread(()->{
  19. while (!P2){
  20. LockSupport.park();
  21. }
  22. if (P2){
  23. System.out.println("B2");
  24. P1=true;
  25. LockSupport.unpark(t1);
  26. }
  27. },"B");
  28. t2.start();
  29. }
  30. }

练习

  • 线程1 输出 a 5次, 线程2 输出 b 5次, 线程3 输出 c 5次。现在要求输出 abcabcabcabcabcabc

wait/notify版本

await/signal版本

LockSupport的park/unpark实现

```java

```java

```java