# 死锁、ReentranLock(锁重入、锁超时、可中断、公平锁、条件变量)、同步模式之顺序控制

死锁、ReentranLock(锁重入、锁超时、可中断、公平锁、条件变量)、同步模式之顺序控制

在这里插入图片描述

多把锁

  • 小故事
  • 一间大屋子有两个功能:睡觉、学习,互不相干
  • 现在小南要学习、小女要睡觉,但如果只用一间屋子(一个对象锁)的话,那么并发度很低
    • 小南获取锁之后,学完习之后,小女才能进来睡觉。
  • 解决方法时准备多个房间(多个对象锁
@Slf4j(topic = "guizy.BigRoomTest")
public class BigRoomTest {
    public static void main(String[] args) {
        BigRoom bigRoom = new BigRoom();
        new Thread(() -> bigRoom.sleep(), "小南").start();
        new Thread(() -> bigRoom.study(), "小女").start();
    }
}

@Slf4j(topic = "guizy.BigRoom")
class BigRoom {
    @SneakyThrows
    public void sleep() {
        synchronized (this) {
            log.debug("sleeping 2 小时");
            Thread.sleep(2);
        }
    }

    @SneakyThrows
    public void study() {
        synchronized (this) {
            log.debug("study 1 小时");
            Thread.sleep(1);
        }
    }
}
// 相当于串行执行, 因为锁对象是整个屋子, 所以并发性很低
12:16:15.952 guizy.BigRoom [小南] - sleeping 2 小时
        12:16:17.954 guizy.BigRoom [小女] - study 1 小时
  • 改进让小南, 小女获取不同的锁即可
@Slf4j(topic = "guizy.BigRoomTest")
public class BigRoomTest2 {

        private static final BigRoom sleepRoom = new BigRoom();
        private static final BigRoom studyRoom = new BigRoom();

        public static void main(String[] args) {
            // 不同对象调用
            new Thread(() -> sleepRoom.sleep(), "小南").start();
            new Thread(() -> studyRoom.study(), "小女").start();
        }

}
// 因为使用的是不同的锁对象
12:18:50.580 guizy.BigRoom [小女] - study 1 小时
        12:18:50.580 guizy.BigRoom [小南] - sleeping 2 小时
  • 将锁的粒度细分
    • 好处,是容易增加并发度
    • 坏处,如果一个线程需要同时获得多把锁,就容易发生死锁

活跃性(死锁)

  • 因为某种原因,是的代码一直无法执行完毕,这样的现象叫做 活跃性
  • 活跃性相关的一系列问题都可以用 ReentranLock进行解决

死锁(重点)

  • 有这样的情况:一个线程需要 同时获得多把锁,这时候就容易发生死锁

如:线程1获取A对象锁,线程2获取B对象锁,此时线程1又想获取B对象锁,线程2又想获取A对象锁,它们都等着对象释放锁,此时就称为死锁

public class DeadLock {
    public static void main(String[] args) {
        final Object A = new Object();
        final Object B = new Object();

        new Thread(()->{
            synchronized (A) {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (B) {

                }
            }
        }).start();

        new Thread(()->{
            synchronized (B) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (A) {

                }
            }
        }).start();
    }

}

发生死锁的必要条件(重点)

  • 互斥条件
    • 在一段时间内,一种资源只能被一个进程锁使用
  • 请求和保持条件
    • 进程以及拥有至少一种资源,同时又去申请其他资源,因为其他资源被别的线程所使用,该进程进入阻塞状态,并且不释放自己已有的资源
  • 不可抢占条件
    • 进程对已获得的锁资源在未使用完成前不能被强占,只能在进程使用完后自己是否
  • 循环等待条件
    • 发生死锁时,必然存在一个进程 —资源的循环链

定位死锁的方法

方式一:JPS+JStack进程ID

  • jps先找到JVM进程
  • jstack 进程ID
    • 在Java控制台中的Terminal中输入jps指令可以查看正在运行的线程ID,使用jstack 进程ID可以查看进程状态

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

方式二、 jconsole检测死锁

img

img

死锁举例 - 哲学家就餐问题 (重点)

有五位哲学家,围坐在圆桌旁。

  • 他们只做两件事,思考和吃饭,思考一会吃口饭,吃完饭后接着思考。
  • 吃饭时要用两根筷子吃,桌上共有 5 根筷子,每位哲学家左右手边各有一根筷子。
  • 如果筷子被身边的人拿着,自己就得等待

当每个哲学家即线程持有一根筷子时,他们都在等待另一个线程释放锁,因此造成了死锁。

// 程序只执行了下面的打印, 没有停止, 没有打印
15:04:55.346 guizy.Philosopher [苏格拉底] - eating...
15:04:55.346 guizy.Philosopher [亚里士多德] - eating...
15:04:55.850 guizy.Philosopher [亚里士多德] - eating...
15:04:55.850 guizy.Philosopher [苏格拉底] - eating...
15:04:56.351 guizy.Philosopher [亚里士多德] - eating...
15:04:56.852 guizy.Philosopher [亚里士多德] - eating...

通过jps, jstack 进程id查看死锁原因
Found one Java-level deadlock: 发现了一个Java级别的死锁

Found one Java-level deadlock:
        =============================
        "阿基米德":
        waiting to lock monitor 0x000000001ae2a358 (object 0x00000000d6ea7420, a com.guizy.reentrantlock.Chopstick),
        which is held by "苏格拉底"
        "苏格拉底":
        waiting to lock monitor 0x0000000017fb3518 (object 0x00000000d6ea7430, a com.guizy.reentrantlock.Chopstick),
        which is held by "柏拉图"
        "柏???图":
        waiting to lock monitor 0x0000000017fb3468 (object 0x00000000d6ea7440, a com.guizy.reentrantlock.Chopstick),
        which is held by "亚里士多德"
        "亚里士多德":
        waiting to lock monitor 0x0000000017fb0bd8 (object 0x00000000d6ea7450, a com.guizy.reentrantlock.Chopstick),
        which is held by "赫拉克利特"
        "赫拉克利特":
        waiting to lock monitor 0x0000000017fb0c88 (object 0x00000000d6ea7460, a com.guizy.reentrantlock.Chopstick),
        which is held by "阿基米德"

        Java stack information for the threads listed above:
        ===================================================
        "阿基米德":
        at com.guizy.reentrantlock.Philosopher.run(PhilosopherEat.java:47)
        - waiting to lock <0x00000000d6ea7420> (a com.guizy.reentrantlock.Chopstick)
        - locked <0x00000000d6ea7460> (a com.guizy.reentrantlock.Chopstick)
        "苏格拉底":
        at com.guizy.reentrantlock.Philosopher.run(PhilosopherEat.java:47)
        - waiting to lock <0x00000000d6ea7430> (a com.guizy.reentrantlock.Chopstick)
        - locked <0x00000000d6ea7420> (a com.guizy.reentrantlock.Chopstick)
        "柏拉图":
        at com.guizy.reentrantlock.Philosopher.run(PhilosopherEat.java:47)
        - waiting to lock <0x00000000d6ea7440> (a com.guizy.reentrantlock.Chopstick)
        - locked <0x00000000d6ea7430> (a com.guizy.reentrantlock.Chopstick)
        "亚里士多德":
        at com.guizy.reentrantlock.Philosopher.run(PhilosopherEat.java:47)
        - waiting to lock <0x00000000d6ea7450> (a com.guizy.reentrantlock.Chopstick)
        - locked <0x00000000d6ea7440> (a com.guizy.reentrantlock.Chopstick)
        "赫拉克利特":
        at com.guizy.reentrantlock.Philosopher.run(PhilosopherEat.java:47)
        - waiting to lock <0x00000000d6ea7460> (a com.guizy.reentrantlock.Chopstick)
        - locked <0x00000000d6ea7450> (a com.guizy.reentrantlock.Chopstick)

        Found 1 deadlock.

避免死锁的方法

  • 在线程使用锁对象时,采用 固定加锁的顺序,可以使用Hash值的大小来确定加锁的先后
  • 尽可能缩减加锁的范围,等到操作共享变量的时候才加锁
  • 使用可释放的定时锁(一段时间申请不到锁的权限了,直接释放掉)

img

活锁

  • 活锁出现在两个线程互相改变对方的结束条件,谁也无法结束

避免活锁的方法

  • 在线程执行时,中途给予不同的间隔时间,让某个线程先结束计即可。

死锁与活锁的区别

  • 死锁时因为线程互相持有对象想要的锁,并且都不释放锁,最后到时 线程阻塞,停止运行的现象。
  • 活锁是因为线程间修改了对方的结束条件,而导致代码 一直在运行,却一直 运行不完的现象。

饥饿

  • 某些线程因为优先级太低,导致一直无法获得资源的现象。(finally线程)
  • 在使用顺序加锁时,可能会出现饥饿现象

ReentrantLock (重点)

ReentranLock 的特点(synchronize不具备的)

  • 支持锁重入

    • 可重入锁是指同一个线程如果首次获得了锁,那么因为它是这把锁的拥有者,因此 有权利再次获得这把锁
  • 可中断

    • lock.lockInterruptibly():可以被其他线程打断的中断锁
  • 可以设置超时时间

    • lock.tryLock(time):尝试获取锁对象,如果超过了设计的时间,还没有获取到锁,此时就退出阻塞队列,并释放自己拥有的锁
  • 可以设置为公平锁

    • (先到先得)默认是非公平,true为公平 new ReentranLock(true)
  • 支持多个条件变量(有多个waitset

    • (可避免虚假唤醒)–lock.newCondition()创建条件变量对象;通过条件变量对象调用await/singal方法,等待/唤醒

ReentrantLock特点

支持锁重入
  • 可重入锁是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此 有权利再次获取这把锁
  • 如果是不可重入锁,那么第二次获取锁时,自己也会被锁住
DEBUG [main] - entry main...
        DEBUG [main] - entry m1...
        DEBUG [main] - entry m2....
可中断(针对于lockInterruptibly()方法获得的中断锁)直接退出阻塞队列,获取锁失败

synchronizereentranlock.lock()的锁,是不可被打断的,也就是说别的现场已经获得了锁,我的线程就需要一直等待下去 不能中断

  • 可被中断的锁,通过lock.lockInterruptibly()获取的锁对象,可以通过 阻塞线程的interrupt()方法
  • 如果某个线程处于阻塞状态,可以调用其interrupt方法让其停止阻塞获取锁失败
    • 处于阻塞状态的线程,被打断了就不用阻塞了,直接停止运行
  • 可中断的锁,在一定程度上可以 被动的减少死锁 的概率,之所以被动,是因为我们需要手动调用 阻塞线程的interrupt方法

测试可以使用lock.lockInterruptibly()可以从阻塞队列中打断。

14:18:09.145 guizy.ReentrantTest [main] - main线程获得了锁
        14:18:09.148 guizy.ReentrantTest [t1] - t1线程启动...
        14:18:10.149 guizy.ReentrantTest [main] - 执行打断
        14:18:10.149 guizy.ReentrantTest [t1] - 等锁的过程中被打断
        java.lang.InterruptedException
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)
        at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
        at com.guizy.reentrantlock.ReentrantTest.lambda$main$0(ReentrantTest.java:25)
        at java.lang.Thread.run(Thread.java:748)

测试使用lock.lock()不可以从阻塞队列中打断, 一直等待别的线程释放锁

14:21:01.329 guizy.ReentrantTest [main] - main线程获得了锁
14:21:01.331 guizy.ReentrantTest [t1] - t1线程启动...
14:21:05.333 guizy.ReentrantTest [main] - main线程执行打断
14:21:05.333 guizy.ReentrantTest [t1] - t1线程获得了锁
锁超时(lock,tryLock()) 直接退出阻塞队列,获取锁失败

防止无限制等待,减少死锁

  • 使用lock.trylock()方法会返回获取锁是否成功。成功成功则返回true,反之则返回fasle。
  • 并且tryLock方法可以设置 **指定等待时间,**参数为:tryLock(long timeout,TimeUnit unit),其中timeout为最长等待时间,TimeUnit为时间单位

获取锁的过程中,如果超过等待时间或者被打断,就直接从阻塞队列移除,此时获取锁就失败了,不会一直阻塞着!(可以用来实现死锁问题)

  • 不设置等待时间, 立即失败
@Slf4j(topic = "guizy.ReentrantTest")
public class ReentrantTest4 {

    private static final ReentrantLock lock = new ReentrantLock();

    @SneakyThrows
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            log.debug("尝试获得锁");
            // 此时肯定获取失败, 因为主线程已经获得了锁对象
            if (!lock.tryLock()) {
                log.debug("获取立刻失败,返回");
                return;
            }
            try {
                log.debug("获得到锁");
            } finally {
                lock.unlock();
            }
        }, "t1");

        lock.lock();
        log.debug("获得到锁");
        t1.start();
        // 主线程2s之后才释放锁
        Thread.sleep(2);
        log.debug("释放了锁");
        lock.unlock();
    }
}


代码结果如下:

14:52:19.726 guizy.WaitNotifyTest [main] - 获得到锁
        14:52:19.728 guizy.WaitNotifyTest [t1] - 尝试获得锁
        14:52:19.728 guizy.WaitNotifyTest [t1] - 获取立刻失败,返回
        14:52:21.728 guizy.WaitNotifyTest [main] - 释放了锁
  • 设置等待时间, 超过等待时间还没有获得锁, 失败, 从阻塞队列移除该线程
@Slf4j(topic = "guizy.ReentrantTest")
public class ReentrantTest5 {

    private static final ReentrantLock lock = new ReentrantLock();

    @SneakyThrows
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            log.debug("尝试获得锁");
            try {
                // 设置等待时间, 超过等待时间 / 被打断, 都会获取锁失败; 退出阻塞队列
                if (!lock.tryLock(1, TimeUnit.SECONDS)) {
                    log.debug("获取锁超时,返回");
                    return;
                }
            } catch (InterruptedException e) {
                log.debug("被打断了, 获取锁失败, 返回");
                e.printStackTrace();
                return;
            }
            try {
                log.debug("获得到锁");
            } finally {
                lock.unlock();
            }
        }, "t1");

        lock.lock();
        log.debug("获得到锁");
        t1.start();
//        t1.interrupt();
        // 主线程2s之后才释放锁
        Thread.sleep(2);
        log.debug("main线程释放了锁");
        lock.unlock();
    }
}

运行结果如下:

DEBUG [main] - 获得到锁
        DEBUG [t1] - 尝试获得锁
        DEBUG [main] - main线程释放了锁
        DEBUG [t1] - 获得到锁
        
        // 中断的打印
14:56:41.258 guizy.WaitNotifyTest [main] - 获得到锁
14:56:41.260 guizy.WaitNotifyTest [main] - main线程释放了锁
14:56:41.261 guizy.WaitNotifyTest [t1] - 尝试获得锁
14:56:41.261 guizy.WaitNotifyTest [t1] - 被打断了, 获取锁失败, 返回
通过lock.tryLock()来解决,哲学家就餐问题(重点)

lock.tryLock(时间):尝试获取锁对象,如果超过了设置时间,还没有获取到锁,此时就退出阻塞队列,并释放掉自己拥有的锁

package com.yc.test2;

/**
 * @author HillCheung
 * @version 1.0
 * @date 2021/4/12 19:05
 */

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Description: ReentrantLock, 演示RenntrantLock中的tryLock(long mills), 超过锁设置的等待时间,就从阻塞队列移除
 *
 * @author guizy1
 * @date 2020/12/23 13:50
 */
@Slf4j(topic = "guizy.ReentrantTest")
public class ReentrantTest5 {

    private static final ReentrantLock lock = new ReentrantLock();

    @SneakyThrows
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            log.debug("尝试获得锁");
            try {
                // 设置等待时间, 超过等待时间 / 被打断, 都会获取锁失败; 退出阻塞队列
                if (!lock.tryLock(1, TimeUnit.SECONDS)) {
                    log.debug("获取锁超时,返回");
                    return;
                }
            } catch (InterruptedException e) {
                log.debug("被打断了, 获取锁失败, 返回");
                e.printStackTrace();
                return;
            }
            try {
                log.debug("获得到锁");
            } finally {
                lock.unlock();
            }
        }, "t1");

        lock.lock();
        log.debug("获得到锁");
        t1.start();
//        t1.interrupt();
        // 主线程2s之后才释放锁
        Thread.sleep(2);
        log.debug("main线程释放了锁");
        lock.unlock();
    }
}
DEBUG [苏格拉底] - eating...
        DEBUG [亚里士多德] - eating...
        DEBUG [苏格拉底] - eating...
        DEBUG [亚里士多德] - eating...
        DEBUG [苏格拉底] - eating...
        DEBUG [亚里士多德] - eating...
        DEBUG [苏格拉底] - eating...
        DEBUG [亚里士多德] - eating...
        DEBUG [苏格拉底] - eating...
        DEBUG [亚里士多德] - eating...
        DEBUG [苏格拉底] - eating...
        DEBUG [亚里士多德] - eating...
        DEBUG [苏格拉底] - eating...
        DEBUG [亚里士多德] - eating...
        DEBUG [苏格拉底] - eating...
        DEBUG [亚里士多德] - eating...
        DEBUG [苏格拉底] - eating...
        DEBUG [亚里士多德] - eating...
        DEBUG [苏格拉底] - eating...
        DEBUG [亚里士多德] - eating...
        DEBUG [苏格拉底] - eating...
        DEBUG [亚里士多德] - eating...
        DEBUG [苏格拉底] - eating...
        DEBUG [亚里士多德] - eating...
        DEBUG [苏格拉底] - eating...
        DEBUG [亚里士多德] - eating...
        DEBUG [苏格拉底] - eating...
        DEBUG [亚里士多德] - eating...
        DEBUG [苏格拉底] - eating...
        DEBUG [亚里士多德] - eating...
        DEBUG [苏格拉底] - eating...
        DEBUG [亚里士多德] - eating...
        DEBUG [苏格拉底] - eating...
        DEBUG [亚里士多德] - eating...
        DEBUG [苏格拉底] - eating...
        DEBUG [亚里士多德] - eating...
        DEBUG [苏格拉底] - eating...
        DEBUG [亚里士多德] - eating...
        DEBUG [苏格拉底] - eating...
     
公平锁 new ReentranLock(true)
  • ReentranLock默认是非公平锁,可以指定为公平锁。
  • 在线程获取锁失败,进入阻塞队列时, 先进入的会在锁被释放后 先获得锁。这样的获取方式就是 公平的。一般不设置 ReentranLock为公平,会降低并发性
  • Synchronize底层的Monitor锁就是不公平的,和谁先进入阻塞队列是没有关系的。
//默认是不公平锁,需要在创建时指定为公平锁
ReentrantLock lock = new ReentrantLock(true);

什么是公平锁?什么是非公平锁?

公平锁(new ReentranLock(true))

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

非公平锁(synchronize,new ReentranLock())

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

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

  • 如果不进入队列呢?线程一直尝试获取锁不就行了?
    • 一直尝试获取锁,在Synchronize轻量级锁升级为重量级锁时,做的一个优化 ,叫做自旋锁, 一般很消耗资源,cpu一直空转,最后获取锁也失败,所以不推荐使用。在Jdk6对于自旋锁有一个机制,在重试获取锁指定次数就失败等等
条件变量(可避免虚假唤醒) -lock.newCondition()创建条件变量对象;通过条件变量对象调用 await/signal方法,等待/唤醒
  • synchronize中也有条件变量,就是Monitor监视器中的waitSet等待集合,当条件不满足时进入waitSet等待
  • ReentranLock的条件变量比synchronize强大之处在于,它是支持多个条件变量。
  • 这就好比synchronize是哪些不满足条件的线程都在一间休息室等通知;(此时会造成虚假唤醒),而ReentranLock支持多间休息室,有专门等烟的休息室、专门等造成的休息室、唤醒时也是按休息室来唤醒;(可以避免虚假唤醒)

使用要点:

  • await前需要 获得锁
  • await执行后,会释放锁,进入conditionObject(条件变量)中等待
  • await的线程被唤醒(或打断、或超时)取重新竞争lock锁
    • 竞争lock锁成功后,从await后继续执行
  • signal方法用来唤醒条件变量(等待室)汇总的某一个等待的线程
  • signalAll方法,唤醒条件变量(休息室)中的所有线程
DEBUG [小南] - 有烟没?[false]
        DEBUG [小南] - 没烟,先歇会!
        DEBUG [小女] - 外卖送到没?
        DEBUG [小女] - 没外卖,先歇会!
        DEBUG [送外卖的] - 送外卖的来了~~
        DEBUG [小女] - 外面来了,可以开始干活了!
        DEBUG [送烟的] - 送烟的来咯~

同步模式之顺序控制(案例)

  • 假设有两个线程,线程A打印1,线程B打印2
  • 要求: 程序先打印2,再打印1

1、Wait/Notify版本实现

@Slf4j
public class SyncPrintWaitTest {
    public static final Object lock =new Object();
    //t2线程释放执行过
    public static boolean t2Runned =false;

    public static void main(String[] args) {
        Thread t1 =new  Thread(()->{
            synchronized (lock){
                while(!t2Runned){
                    try {
                        //进入等待(waitSet),会释放锁
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }finally {

                    }
                }
            }
            log.debug("1");
        },"t1");


        Thread t2 =new Thread(()->{
            synchronized (lock){
                log.debug("2");
                t2Runned=true;
                lock.notifyAll();
            }
        },"t2");
        t1.start();
        t2.start();
    }

}

2、使用ReentrantLock的await/signal

public class SyncPrintWaitTest2 {
    public static final ReentrantLock lock =new ReentrantLock();;
    public static Condition condition =lock.newCondition();
    //t2线程释放执行过
    public static boolean t2Runned =false;

    public static void main(String[] args) {
        Runnable target;
        Thread t1 =new Thread(() ->{
            lock.lock();
            try {
                //临界区
                while(!t2Runned){
                    try {
                        condition.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("1");
            }finally {
                lock.unlock();
            }
        },"t1");

        Thread t2 =new Thread(()->{
            lock.lock();
            try {
                log.debug("2");
                t2Runned=true;
                condition.signal();
            }finally {
                lock.unlock();
            }
        },"t2");
        t1.start();
        t2.start();
    }
}
public class SyncPrintWaitTest3 {
    public static void main(String[] args) {
        Runnable target;
        Thread t1 =new Thread(()->{
            LockSupport.park();
            log.debug("1");
        },"t1");
        t1.start();

        new Thread(()->{
            log.debug("2");
            LockSupport.unpark(t1);
        },"t2").start();
    }
}

交替输出

需求

  • 线程1 输出 a 5次, 线程2 输出 b 5次, 线程3 输出 c 5次。现在要求输出 abcabcabcabcabcabc
wait/notify版本
@Slf4j(topic = "TestWaitNotify")
public class TestWaitNotify {
    public static void main(String[] args) {
        WaitNotify waitNotify =new WaitNotify(1,5);
        new Thread(()->{
            waitNotify.print("a",1,2);
        },"a线程").start();

        new Thread(()->{
            waitNotify.print("b",2,3);
        },"b线程").start();

        new Thread(()->{
            waitNotify.print("c",3,1);
        },"c线程").start();
    }
}
@Data
@AllArgsConstructor
@Slf4j(topic = "WaitNotify")
class WaitNotify{
    private int flag;
    //循环次数
    private int loopNumber;

    public void print(String str,int waitFlag,int nextFlag){
        for (int i = 0; i <loopNumber ; i++) {
            synchronized (this){
                while(waitFlag!=this.flag){
                    try {
                        this.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.print(str);
                this.flag=nextFlag;
                this.notifyAll();
            }
        }
    }
}
2、await/signal版本
package com.yc.test3;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author HillCheung
 * @version 1.0
 * @date 2021/4/12 21:24
 */
@Slf4j
public class TestAwaitSignal {
    public static void main(String[] args) {
        AwaitSigal awaitSigal =new AwaitSigal(5);
        Condition condition_a = awaitSigal.newCondition();
        Condition condition_b = awaitSigal.newCondition();
        Condition condition_c = awaitSigal.newCondition();

        new Thread(()->{
            awaitSigal.print("a",condition_a,condition_b);
        },"a").start();

        new Thread(()->{
            awaitSigal.print("b",condition_b,condition_c);
        },"b").start();

        new Thread(()->{
            awaitSigal.print("c",condition_c,condition_a);
        },"c").start();

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("开始=======");
        awaitSigal.lock();
        try {
            condition_a.signal();   //先唤醒a线程
        }finally {
            awaitSigal.unlock();
        }

    }
}
class AwaitSigal extends ReentrantLock{
    private final  int loopNumber;


    AwaitSigal(int loopNumber) {
        this.loopNumber = loopNumber;
    }

    public void print(String str, Condition condition,Condition next){
        for (int i = 0; i < loopNumber; i++) {
            lock();
            try {
                try {
                    condition.await();      //睡眠
                    System.out.print(str);
                    next.signal();      //唤醒
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }finally {
                unlock();
            }
        }
    }
}
3、LockSupport的park/unpark实现
package com.yc.test3;

/**
 * @author HillCheung
 * @version 1.0
 * @date 2021/4/13 10:49
 */
/**
 * Description: 使用park/unpark来实现三个线程交替打印abcabcabcabcabc
 *
 * @author guizy1
 * @date 2020/12/23 17:12
 */
@Slf4j(topic = "guizy.TestWaitNotify")
public class TestParkUnpark {
    static Thread a;
    static Thread b;
    static Thread c;

    public static void main(String[] args) {
        ParkUnpark parkUnpark = new ParkUnpark(5);

        a = new Thread(() -> {
            parkUnpark.print("a", b);
        }, "a");

        b = new Thread(() -> {
            parkUnpark.print("b", c);
        }, "b");

        c = new Thread(() -> {
            parkUnpark.print("c", a);
        }, "c");

        a.start();
        b.start();
        c.start();

        LockSupport.unpark(a);

    }
}

class ParkUnpark {
    private final int loopNumber;

    public ParkUnpark(int loopNumber) {
        this.loopNumber = loopNumber;
    }

    public void print(String str, Thread nextThread) {
        for (int i = 0; i < loopNumber; i++) {
            LockSupport.park();
            System.out.print(str);
            LockSupport.unpark(nextThread);
        }
    }
}

小结

在这里插入图片描述

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值