JUC并发编程系列文章
http://t.csdn.cn/UgzQi
文章目录
- JUC并发编程系列文章
- 前言
- 七、wait -- notify
- 1、为什么使用 wait
- 2、 wait -- notify 原理
- 3、API 介绍
- 八、wait -- notify 的正确姿势
- 示例一:sleep会拿着锁睡觉,阻碍其它线程执行
- 示例二:wait -- notify 替代sleep
- 示例三:wait -- notify 唤醒错了线程,产生虚假唤醒
- 示例四:使用notifyAll唤醒所有线程
- 示例五:while+wait 彻底解决虚假唤醒
- 总结:wait -- notify 正确使用姿势
- (同步)模式之保护性暂停
- 实现:GuardedObject
- 应用:GuardedObject
- 扩展1:带超时版 GuardedObject
- 测试没有超时
- 测试超时
- join原理:底层是wait,使用了保护性暂停模式
- 扩展2:多任务版 GuardedObject ,一对一线程通信
- (异步)模式之生产者/ 消费者
- 九、park和Unpark
- park和Unpark的原理
- 先调用park再调用Unpark
- 先调用Unpark 再调用park
- 十、重新理解线程状态的转换
- 情况一和情况二
- 情况三和情况四
- 情况五
- 情况六
- 情况七
- 情况八
- 情况九
- 情况十
- 十一、多把锁
- 单把锁造成并发度低
- 改进单把对象锁的问题
- 十二、活跃性
- 死锁
- 定位死锁 P116
- 哲学家就餐问题,导致死锁的经典案例
- 活锁
- 饥饿
- 顺序加锁解决死锁问题 ,但容易产生饥饿问题
- 十三、ReentrantLock (可重入的)
- 可重入锁
- 可打断 lock.lockInterruptibly()
- (可设置)锁超时 lock.tryLock()🍓
- lock.tryLock()无参,获取不到锁立刻返回,不会等待
- lock.tryLock(TimeOut,TimeUnit) 有参,等待时间一到,没有获得锁立即返回
- lock.tryLock()解决哲学家就餐问题💕
- 公平锁
- 条件变量:ReentrantLock的多个waitSet休息室
- 同步模式之顺序控制🍕设计模式
- 固定运行顺序
- wait -- notify 版
- ReentrantLock -- Condition 版
- park -- Unpark 版
- 交替输出
- wait -- notify 版
- ReentrantLock -- Condition 版
- park -- Unpark 版
- 本章总结
前言
不积硅步无以至千里,不积小流无以成江海。
七、wait – notify
1、为什么使用 wait
2、 wait – notify 原理
3、API 介绍
final static Object obj = new Object();
public static void main(String[] args) {
new Thread(() -> {
synchronized (obj) {
log.debug("执行....");
try {
obj.wait(); // 让线程在obj上一直等待下去
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("其它代码....");
}
}).start();
new Thread(() -> {
synchronized (obj) {
log.debug("执行....");
try {
obj.wait(); // 让线程在obj上一直等待下去
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("其它代码....");
}
}).start();
// 主线程两秒后执行
sleep(2);
log.debug("唤醒 obj 上其它线程");
synchronized (obj) {
obj.notify(); // 唤醒obj上一个线程
// obj.notifyAll(); // 唤醒obj上所有等待线程
}
}
notify 的一种结果
20:00:53.096 [Thread-0] c.TestWaitNotify - 执行....
20:00:53.099 [Thread-1] c.TestWaitNotify - 执行....
20:00:55.096 [main] c.TestWaitNotify - 唤醒 obj 上其它线程
20:00:55.096 [Thread-0] c.TestWaitNotify - 其它代码....
notifyAll 的结果
19:58:15.457 [Thread-0] c.TestWaitNotify - 执行....
19:58:15.460 [Thread-1] c.TestWaitNotify - 执行....
19:58:17.456 [main] c.TestWaitNotify - 唤醒 obj 上其它线程
19:58:17.456 [Thread-1] c.TestWaitNotify - 其它代码....
19:58:17.456 [Thread-0] c.TestWaitNotify - 其它代码....
wait() 方法会释放对象的锁,进入 WaitSet 等待区,从而让其他线程就机会获取对象的锁。无限制等待,直到notify 为止
wait(long n) 有时限的等待, 到 n 毫秒后结束等待,或是被 notify
八、wait – notify 的正确姿势
示例一:sleep会拿着锁睡觉,阻碍其它线程执行
思考下面的解决方案好不好,为什么?
static final Object room = new Object();
static boolean hasCigarette = false;
static boolean hasTakeout = false;
new Thread(() -> {
synchronized (room) {
log.debug("有烟没?[{}]", hasCigarette);
if (!hasCigarette) {
log.debug("没烟,先歇会!");
sleep(2);
}
log.debug("有烟没?[{}]", hasCigarette);
if (hasCigarette) {
log.debug("可以开始干活了");
}
}
}, "小南").start();
for (int i = 0; i < 5; i++) {
new Thread(() -> {
synchronized (room) {
log.debug("可以开始干活了");
}
}, "其它人").start();
}
sleep(1);
new Thread(() -> {
// 这里能不能加 synchronized (room)? 不能
hasCigarette = true;
log.debug("烟到了噢!");
}, "送烟的").start();
//输出
20:49:49.883 [小南] c.TestCorrectPosture - 有烟没?[false]
20:49:49.887 [小南] c.TestCorrectPosture - 没烟,先歇会!
20:49:50.882 [送烟的] c.TestCorrectPosture - 烟到了噢!
20:49:51.887 [小南] c.TestCorrectPosture - 有烟没?[true]
20:49:51.887 [小南] c.TestCorrectPosture - 可以开始干活了
20:49:51.887 [其它人] c.TestCorrectPosture - 可以开始干活了
20:49:51.887 [其它人] c.TestCorrectPosture - 可以开始干活了
20:49:51.888 [其它人] c.TestCorrectPosture - 可以开始干活了
20:49:51.888 [其它人] c.TestCorrectPosture - 可以开始干活了
20:49:51.888 [其它人] c.TestCorrectPosture - 可以开始干活了
● 其它干活的线程,都要一直阻塞,效率太低
● 小南线程必须睡足 2s 后才能醒来,就算烟提前送到,也无法立刻醒来
● 加了 synchronized (room) 后,就好比小南在里面反锁了门睡觉,烟根本没法送进门,main 没加 synchronized 就好像 main 线程是翻窗户进来的
● sleep妨碍其它人干活 解决方法,使用 wait - notify
示例二:wait – notify 替代sleep
● 解决了其它干活的线程阻塞的问题
● 但如果有其它线程也在等待条件呢?
但是如果有很多线程都在 wait 等待,notify 会不会叫错人,本来要唤醒线程 1 起来干活,结果把线程2 给叫醒了,但是线程 2的数据还没有准备好,这时又该怎么办?
new Thread(() -> {
synchronized (room) {
log.debug("有烟没?[{}]", hasCigarette);
if (!hasCigarette) {
log.debug("没烟,先歇会!");
try {
room.wait(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("有烟没?[{}]", hasCigarette);
if (hasCigarette) {
log.debug("可以开始干活了");
}
}
}, "小南").start();
for (int i = 0; i < 5; i++) {
new Thread(() -> {
synchronized (room) {
log.debug("可以开始干活了");
}
}, "其它人").start();
}
sleep(1);
new Thread(() -> {
synchronized (room) {
hasCigarette = true;
log.debug("烟到了噢!");
room.notify();
}
}, "送烟的").start();
输出
20:51:42.489 [小南] c.TestCorrectPosture - 有烟没?[false]
20:51:42.493 [小南] c.TestCorrectPosture - 没烟,先歇会!
20:51:42.493 [其它人] c.TestCorrectPosture - 可以开始干活了
20:51:42.493 [其它人] c.TestCorrectPosture - 可以开始干活了
20:51:42.494 [其它人] c.TestCorrectPosture - 可以开始干活了
20:51:42.494 [其它人] c.TestCorrectPosture - 可以开始干活了
20:51:42.494 [其它人] c.TestCorrectPosture - 可以开始干活了
20:51:43.490 [送烟的] c.TestCorrectPosture - 烟到了噢!
20:51:43.490 [小南] c.TestCorrectPosture - 有烟没?[true]
20:51:43.490 [小南] c.TestCorrectPosture - 可以开始干活了
示例三:wait – notify 唤醒错了线程,产生虚假唤醒
● notify 只能随机唤醒一个 WaitSet 中的线程,这时如果有其它线程也在等待,那么就可能唤醒不了正确的线程,称之为【虚假唤醒】
● 发生虚假唤醒: 解决方法,改为 notifyAll
new Thread(() -> {
synchronized (room) {
log.debug("有烟没?[{}]", hasCigarette);
if (!hasCigarette) {
log.debug("没烟,先歇会!");
try {
room.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("有烟没?[{}]", hasCigarette);
if (hasCigarette) {
log.debug("可以开始干活了");
} else {
log.debug("没干成活...");
}
}
}, "小南").start();
new Thread(() -> {
synchronized (room) {
Thread thread = Thread.currentThread();
log.debug("外卖送到没?[{}]", hasTakeout);
if (!hasTakeout) {
log.debug("没外卖,先歇会!");
try {
room.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("外卖送到没?[{}]", hasTakeout);
if (hasTakeout) {
log.debug("可以开始干活了");
} else {
log.debug("没干成活...");
}
}
}, "小女").start();
sleep(1);
new Thread(() -> {
synchronized (room) {
hasTakeout = true;
log.debug("外卖到了噢!");
room.notify();
}
}, "送外卖的").start();
输出 造成虚假唤醒
20:53:12.173 [小南] c.TestCorrectPosture - 有烟没?[false]
20:53:12.176 [小南] c.TestCorrectPosture - 没烟,先歇会!
20:53:12.176 [小女] c.TestCorrectPosture - 外卖送到没?[false]
20:53:12.176 [小女] c.TestCorrectPosture - 没外卖,先歇会!
20:53:13.174 [送外卖的] c.TestCorrectPosture - 外卖到了噢!
20:53:13.174 [小南] c.TestCorrectPosture - 有烟没?[false]
20:53:13.174 [小南] c.TestCorrectPosture - 没干成活...
外卖送到了,小女没能干成活
示例四:使用notifyAll唤醒所有线程
就算唤醒了所有线程,这样可以保证能拿到结果的线程执行业务,但是那些拿不到结果的线程也被唤醒了,这时被唤醒完全没有屁用,如果没有拿到结果的线程很多,无疑是脱裤子放屁。那怎么解决这个问题示例五会给出答案
new Thread(() -> {
synchronized (room) {
hasTakeout = true;
log.debug("外卖到了噢!");
room.notifyAll();
}
}, "送外卖的").start();
输出
20:55:23.978 [小南] c.TestCorrectPosture - 有烟没?[false]
20:55:23.982 [小南] c.TestCorrectPosture - 没烟,先歇会!
20:55:23.982 [小女] c.TestCorrectPosture - 外卖送到没?[false]
20:55:23.982 [小女] c.TestCorrectPosture - 没外卖,先歇会!
20:55:24.979 [送外卖的] c.TestCorrectPosture - 外卖到了噢!
20:55:24.979 [小女] c.TestCorrectPosture - 外卖送到没?[true]
20:55:24.980 [小女] c.TestCorrectPosture - 可以开始干活了
20:55:24.980 [小南] c.TestCorrectPosture - 有烟没?[false]
20:55:24.980 [小南] c.TestCorrectPosture - 没干成活...
notifyAll唤醒了所有,但使用if+wait仅有一次机会,解决方法,一旦条件不成立,就没有重新判断的机会了.解决办法: 用 while + wait,当条件不成立,再次 wait
示例五:while+wait 彻底解决虚假唤醒
//if 判断改成 while
if (!hasCigarette) {
log.debug("没烟,先歇会!");
try {
room.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
线程被虚假唤醒后,会来到 room.wait() 方法,这时还是处于循环状态
继续执行代码,判断条件是否成立,成立的话再让当前线程继续调用 wai() 方法等待
while (!hasCigarette) {
log.debug("没烟,先歇会!");
try {
room.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
总结:wait – notify 正确使用姿势
synchronized(lock) {
while(条件不成立) {
lock.wait();
}
// 干活
}
//另一个线程
synchronized(lock) {
lock.notifyAll();
}
(同步)模式之保护性暂停
实现:GuardedObject
class GuardedObject {
private Object response;
private final Object lock = new Object();
public Object get() {
synchronized (lock) {
// 条件不满足则等待
while (response == null) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return response;
}
}
public void complete(Object response) {
synchronized (lock) {
// 条件满足,通知等待线程
this.response = response;
lock.notifyAll();
}
}
}
应用:GuardedObject
一个线程等待另一个线程的执行结果
public static void main(String[] args) {
GuardedObject guardedObject = new GuardedObject();
new Thread(() -> {
try {
// 子线程执行下载
List<String> response = download();
log.debug("download complete...");
guardedObject.complete(response);
} catch (IOException e) {
e.printStackTrace();
}
}).start();
log.debug("waiting...");
// 主线程阻塞等待
Object response = guardedObject.get();
log.debug("get response: [{}] lines", ((List<String>) response).size());
}
执行结果
08:42:18.568 [main] c.TestGuardedObject - waiting...
08:42:23.312 [Thread-0] c.TestGuardedObject - download complete...
08:42:23.312 [main] c.TestGuardedObject - get response: [3] lines
扩展1:带超时版 GuardedObject
class GuardedObjectV2 {
private Object response;
private final Object lock = new Object();
public Object get(long millis) {
synchronized (lock) {
// 1) 记录最初时间
long begin = System.currentTimeMillis();
// 2) 已经经历的时间
long timePassed = 0;
while (response == null) {
// 4) 假设 millis 是 1000,结果在 400 时唤醒了,那么还有 600 要等
long waitTime = millis - timePassed;
log.debug("waitTime: {}", waitTime);
if (waitTime <= 0) {
log.debug("break...");
break;
}
try {
lock.wait(waitTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 3) 如果提前被唤醒,这时已经经历的时间假设为 400
timePassed = System.currentTimeMillis() - begin;
log.debug("timePassed: {}, object is null {}",
timePassed, response == null);
}
return response;
}
}
public void complete(Object response) {
synchronized (lock) {
// 条件满足,通知等待线程
this.response = response;
log.debug("notify...");
lock.notifyAll();
}
}
}
测试没有超时
public static void main(String[] args) {
GuardedObjectV2 v2 = new GuardedObjectV2();
new Thread(() -> {
sleep(1);
v2.complete(null);
sleep(1);
v2.complete(Arrays.asList("a", "b", "c"));
}).start();
Object response = v2.get(2500);
if (response != null) {
log.debug("get response: [{}] lines", ((List<String>) response).size());
} else {
log.debug("can't get response");
}
}
输出
08:49:39.917 [main] c.GuardedObjectV2 - waitTime: 2500
08:49:40.917 [Thread-0] c.GuardedObjectV2 - notify...
08:49:40.917 [main] c.GuardedObjectV2 - timePassed: 1003, object is null true
08:49:40.917 [main] c.GuardedObjectV2 - waitTime: 1497
08:49:41.918 [Thread-0] c.GuardedObjectV2 - notify...
08:49:41.918 [main] c.GuardedObjectV2 - timePassed: 2004, object is null false
08:49:41.918 [main] c.TestGuardedObjectV2 - get response: [3] lines
测试超时
// 等待时间不足
List<String> lines = v2.get(1500);
输出
08:47:54.963 [main] c.GuardedObjectV2 - waitTime: 1500
08:47:55.963 [Thread-0] c.GuardedObjectV2 - notify...
08:47:55.963 [main] c.GuardedObjectV2 - timePassed: 1002, object is null true
08:47:55.963 [main] c.GuardedObjectV2 - waitTime: 498
08:47:56.461 [main] c.GuardedObjectV2 - timePassed: 1500, object is null true
08:47:56.461 [main] c.GuardedObjectV2 - waitTime: 0
08:47:56.461 [main] c.GuardedObjectV2 - break...
08:47:56.461 [main] c.TestGuardedObjectV2 - can't get response
08:47:56.963 [Thread-0] c.GuardedObjectV2 - notify...
join原理:底层是wait,使用了保护性暂停模式
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;
}
}
}
扩展2:多任务版 GuardedObject ,一对一线程通信
Futures 对象需要维护一个 GuardedObject 的集合,其中每个GuardedObject 都应该有一个唯一的 id
import lombok.extern.slf4j.Slf4j;
import java.util.Hashtable;
import java.util.Map;
import java.util.Set;
@Slf4j(topic = "c.testThread09")
public class testThread09 {
public static void main(String[] args) throws Exception {
for (int i = 0; i < 3; i++) {
new People().start();
}
Thread.sleep(2000);
for (Integer id : Mailboxes.getIds()) {
new Postman(id, "内容" + id).start();
}
/** 输出
* 19:57:39 [Thread-2] c.People - 开始收信 id:2
* 19:57:39 [Thread-0] c.People - 开始收信 id:1
* 19:57:39 [Thread-1] c.People - 开始收信 id:3
* 19:57:41 [Thread-5] c.Postman - 送信 id:1, 内容:内容1
* 19:57:41 [Thread-3] c.Postman - 送信 id:3, 内容:内容3
* 19:57:41 [Thread-4] c.Postman - 送信 id:2, 内容:内容2
* 19:57:41 [Thread-1] c.People - 收到信 id:3, 内容:内容3
* 19:57:41 [Thread-2] c.People - 收到信 id:2, 内容:内容2
* 19:57:41 [Thread-0] c.People - 收到信 id:1, 内容:内容1
*/
}
}
//业务相关类
@Slf4j(topic = "c.Postman")
class Postman extends Thread {
private int id;
private String mail;
public Postman(int id, String mail) {
this.id = id;
this.mail = mail;
}
@Override
public void run() {
GuardedObject guardedObject = Mailboxes.getGuardedObject(id);
log.debug("送信 id:{}, 内容:{}", id, mail);
guardedObject.complete(mail);
}
}
//业务相关类
@Slf4j(topic = "c.People")
class People extends Thread{
@Override
public void run() {
// 收信
GuardedObject guardedObject = Mailboxes.createGuardedObject();
log.debug("开始收信 id:{}", guardedObject.getId());
Object mail = guardedObject.get(5000);
log.debug("收到信 id:{}, 内容:{}", guardedObject.getId(), mail);
}
}
/**
* 中间解耦类
*/
class Mailboxes {
private static Map<Integer, GuardedObject> boxes = new Hashtable<>();
private static int id = 1;
// 产生唯一 id
private static synchronized int generateId() {
return id++;
}
public static GuardedObject getGuardedObject(int id) {
return boxes.remove(id);
}
public static GuardedObject createGuardedObject() {
GuardedObject go = new GuardedObject(generateId());
boxes.put(go.getId(), go);
return go;
}
public static Set<Integer> getIds() {
return boxes.keySet();
}
}
/**
* 新增 id 用来标识 Guarded Object
*/
class GuardedObject {
// 标识 Guarded Object
private int id;
public GuardedObject(int id) {
this.id = id;
}
public int getId() {
return id;
}
// 结果
private Object response;
// 获取结果
// timeout 表示要等待多久 2000
public Object get(long timeout) {
synchronized (this) {
// 开始时间 15:00:00
long begin = System.currentTimeMillis();
// 经历的时间
long passedTime = 0;
while (response == null) {
// 这一轮循环应该等待的时间
long waitTime = timeout - passedTime;
// 经历的时间超过了最大等待时间时,退出循环
if (timeout - passedTime <= 0) {
break;
}
try {
this.wait(waitTime); // 虚假唤醒 15:00:01
} catch (InterruptedException e) {
e.printStackTrace();
}
// 求得经历时间
passedTime = System.currentTimeMillis() - begin; // 15:00:02 1s
}
return response;
}
}
// 产生结果
public void complete(Object response) {
synchronized (this) {
// 给结果成员变量赋值
this.response = response;
this.notifyAll();
}
}
}
(异步)模式之生产者/ 消费者
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
@Slf4j(topic = "c.testThread10")
public class testThread10 {
public static void main(String[] args) {
MessageQueue messageQueue = new MessageQueue(2);
// 4 个生产者线程, 下载任务
for (int i = 0; i < 4; i++) {
int id = i;
new Thread(() -> {
try {
log.debug("try put message({})", id+"值"+id);
messageQueue.put(new Message(id, "值"+id));
} catch (Exception e) {
e.printStackTrace();
}
}, "生产者" + i).start();
}
// 1 个消费者线程, 处理结果
new Thread(() -> {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Message message = messageQueue.take();
log.debug("消费了"+message.toString());
}
}, "消费者").start();
}
}
@Slf4j(topic = "c.Message")
class Message {
private int id;
private Object message;
public Message(int id, Object message) {
this.id = id;
this.message = message;
}
public int getId() {
return id;
}
public Object getMessage() {
return message;
}
@Override
public String toString() {
return "Message{" +
"id=" + id +
", message=" + message +
'}';
}
}
@Slf4j(topic = "c.Message")
class MessageQueue {
private LinkedList<Message> queue;
private int capacity;
public MessageQueue(int capacity) {
this.capacity = capacity;
queue = new LinkedList<>();
}
public Message take() {
synchronized (queue) {
while (queue.isEmpty()) {
log.debug("没货了, wait");
try {
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Message message = queue.removeFirst();
queue.notifyAll();
return message;
}
}
public void put(Message message) {
synchronized (queue) {
while (queue.size() == capacity) {
log.debug("库存已达上限, wait");
try {
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
queue.addLast(message);
queue.notifyAll();
}
}
}
输出
20:57:09 [生产者3] c.testThread10 - try put message(3值3)
20:57:09 [生产者2] c.testThread10 - try put message(2值2)
20:57:09 [生产者0] c.testThread10 - try put message(0值0)
20:57:09 [生产者1] c.testThread10 - try put message(1值1)
20:57:09 [生产者1] c.Message - 库存已达上限, wait
20:57:09 [生产者3] c.Message - 库存已达上限, wait
20:57:10 [生产者1] c.Message - 库存已达上限, wait
20:57:10 [消费者] c.testThread10 - 消费了Message{id=0, message=值0}
20:57:11 [消费者] c.testThread10 - 消费了Message{id=2, message=值2}
20:57:12 [消费者] c.testThread10 - 消费了Message{id=3, message=值3}
20:57:13 [消费者] c.testThread10 - 消费了Message{id=1, message=值1}
20:57:14 [消费者] c.Message - 没货了, wait
九、park和Unpark
Unpark可以在 park之前调用,也可以在park 之后调用,都是用来恢复某个线程的运行,奇怪之处就是可以在线程暂停前调用 Unpark 就可以恢复线程的运行。
特点
与 Object 的 wait & notify 相比
● wait,notify 和 notifyAll 必须配合 Object Monitor 一起使用,而 park,unpark 不必
● park & unpark 是以线程为单位来【阻塞】和【唤醒】线程,而 notify 只能随机唤醒一个等待线程,notifyAll是唤醒所有等待线程,就不那么【精确】
● park & unpark 可以先 unpark,而 wait & notify 不能先 notify
park和Unpark的原理
先调用park再调用Unpark
先调用Unpark 再调用park
十、重新理解线程状态的转换
情况一和情况二
public class TestWaitNotify {
final static Object obj = new Object();
public static void main(String[] args) {
new Thread(() -> {
synchronized (obj) {
log.debug("执行....");
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("其它代码...."); // 断点
}
},"t1").start();
new Thread(() -> {
synchronized (obj) {
log.debug("执行....");
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("其它代码...."); // 断点
}
},"t2").start();
sleep(0.5);
log.debug("唤醒 obj 上其它线程");
synchronized (obj) {
obj.notifyAll(); // 唤醒obj上所有等待线程 断点
}
}
}
情况三和情况四
情况五
情况六
情况七
情况八
情况九
情况十
十一、多把锁
单把锁造成并发度低
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
/**
* 输出:
* 18:55:25 [小南] c.testThread11 - study 1 小时
* 18:55:26 [小女] c.testThread11 - sleeping 2 小时
*
* 结论:
* 如果一个类的两个方法互不相干,这时不能再使用对象锁,锁住整个对象了
* 这时锁住整个对象,并发度会大大降低,也就造成了方法的串行,一个方法结束了才能调用另外一个方法。
*/
@Slf4j(topic = "c.testThread11")
public class testThread11 {
public static void main(String[] args) {
BigRoom bigRoom = new BigRoom();
new Thread(() -> {
bigRoom.study();
},"小南").start();
new Thread(() -> {
bigRoom.sleep();
},"小女").start();
}
}
@Slf4j(topic = "c.testThread11")
class BigRoom {
@SneakyThrows
public void sleep() {
synchronized (this) {
log.debug("sleeping 2 小时");
Thread.sleep(2000);
}
}
@SneakyThrows
public void study() {
synchronized (this) {
log.debug("study 1 小时");
Thread.sleep(1000);
}
}
}
改进单把对象锁的问题
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
/**
* 输出:
* 19:01:38 [小南] c.testThread11 - study 1 小时
* 19:01:38 [小女] c.testThread11 - sleeping 2 小时
*
* 结论:
* 使用两个对象,让互补相干的方法各锁一个,就可以解决单把锁的并发度低的问题
* 单必须保证两把锁锁住的方法没有任何数据的关联。
*/
@Slf4j(topic = "c.testThread11")
public class testThread11 {
public static void main(String[] args) {
BigRoom bigRoom = new BigRoom();
new Thread(() -> {
bigRoom.study();
},"小南").start();
new Thread(() -> {
bigRoom.sleep();
},"小女").start();
}
}
@Slf4j(topic = "c.testThread11")
class BigRoom {
private final Object studyRoom = new Object();
private final Object bedRoom = new Object();
@SneakyThrows
public void sleep() {
synchronized (bedRoom) {
log.debug("sleeping 2 小时");
Thread.sleep(2000);
}
}
@SneakyThrows
public void study() {
synchronized (studyRoom) {
log.debug("study 1 小时");
Thread.sleep(1000);
}
}
}
十二、活跃性
死锁
import lombok.extern.slf4j.Slf4j;
/**
* 两个线程互相持有对方需要的锁就会陷入死锁。
*/
@Slf4j(topic = "c.tsetThread12")
public class tsetThread12 {
public static void main(String[] args) {
test();
}
public static void test(){
Object A = new Object();
Object B = new Object();
Thread t1 = new Thread(() -> {
synchronized (A) {
log.debug("lock A");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (B) {
log.debug("lock B");
log.debug("操作...");
}
}
}, "t1");
Thread t2 = new Thread(() -> {
synchronized (B) {
log.debug("lock B");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (A) {
log.debug("lock A");
log.debug("操作...");
}
}
}, "t2");
t1.start();
t2.start();
}
}
定位死锁 P116
● 避免死锁要注意加锁顺序
● 另外如果由于某个线程进入了死循环,导致其它线程一直等待,对于这种情况 linux 下可以通过 top 先定位到CPU 占用高的 Java 进程,再利用 top -Hp 进程id 来定位是哪个线程,最后再用 jstack 排查
检测死锁可以使用 jconsole工具,或者使用 jps 定位进程 id,再用 jstack 定位死锁
哲学家就餐问题,导致死锁的经典案例
import lombok.extern.slf4j.Slf4j;
/** 造成死锁
* 19:45:50 [亚里士多德] c.Philosopher - eating...
* 19:45:50 [苏格拉底] c.Philosopher - eating...
* 19:45:51 [亚里士多德] c.Philosopher - eating...
* 19:45:51 [苏格拉底] c.Philosopher - eating...
* 19:45:52 [柏拉图] c.Philosopher - eating...
* 19:45:53 [苏格拉底] c.Philosopher - eating...
*
*/
@Slf4j(topic = "c.testThread13")
public class testThread13 {
public static void main(String[] args) {
Chopstick c1 = new Chopstick("1");
Chopstick c2 = new Chopstick("2");
Chopstick c3 = new Chopstick("3");
Chopstick c4 = new Chopstick("4");
Chopstick c5 = new Chopstick("5");
new Philosopher("苏格拉底", c1, c2).start();
new Philosopher("柏拉图", c2, c3).start();
new Philosopher("亚里士多德", c3, c4).start();
new Philosopher("赫拉克利特", c4, c5).start();
new Philosopher("阿基米德", c5, c1).start();
}
}
//筷子类
class Chopstick {
String name;
public Chopstick(String name) {
this.name = name;
}
@Override
public String toString() {
return "筷子{" + name + '}';
}
}
//哲学家类
@Slf4j(topic = "c.Philosopher")
class Philosopher extends Thread {
Chopstick left;
Chopstick right;
public Philosopher(String name, Chopstick left, Chopstick right) {
super(name);
this.left = left;
this.right = right;
}
private void eat() {
log.debug("eating...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void run() {
while (true) {
// 获得左手筷子
synchronized (left) {
// 获得右手筷子
synchronized (right) {
// 吃饭
eat();
}
// 放下右手筷子
}
// 放下左手筷子
}
}
}
使用 jconsole 检测死锁,发现
-------------------------------------------------------------------------
名称: 阿基米德
状态: cn.itcast.Chopstick@1540e19d (筷子1) 上的BLOCKED, 拥有者: 苏格拉底
总阻止数: 2, 总等待数: 1
堆栈跟踪:
cn.itcast.Philosopher.run(TestDinner.java:48)
- 已锁定 cn.itcast.Chopstick@6d6f6e28 (筷子5)
-------------------------------------------------------------------------
名称: 苏格拉底
状态: cn.itcast.Chopstick@677327b6 (筷子2) 上的BLOCKED, 拥有者: 柏拉图
总阻止数: 2, 总等待数: 1
堆栈跟踪:
cn.itcast.Philosopher.run(TestDinner.java:48)
- 已锁定 cn.itcast.Chopstick@1540e19d (筷子1)
-------------------------------------------------------------------------
名称: 柏拉图
状态: cn.itcast.Chopstick@14ae5a5 (筷子3) 上的BLOCKED, 拥有者: 亚里士多德
总阻止数: 2, 总等待数: 0
堆栈跟踪:
cn.itcast.Philosopher.run(TestDinner.java:48)
- 已锁定 cn.itcast.Chopstick@677327b6 (筷子2)
-------------------------------------------------------------------------
名称: 亚里士多德
状态: cn.itcast.Chopstick@7f31245a (筷子4) 上的BLOCKED, 拥有者: 赫拉克利特
总阻止数: 1, 总等待数: 1
堆栈跟踪:
cn.itcast.Philosopher.run(TestDinner.java:48)
- 已锁定 cn.itcast.Chopstick@14ae5a5 (筷子3)
-------------------------------------------------------------------------
名称: 赫拉克利特
状态: cn.itcast.Chopstick@6d6f6e28 (筷子5) 上的BLOCKED, 拥有者: 阿基米德
总阻止数: 2, 总等待数: 0
堆栈跟踪:
cn.itcast.Philosopher.run(TestDinner.java:48)
- 已锁定 cn.itcast.Chopstick@7f31245a (筷子4)
这种线程没有按预期结束,执行不下去的情况,归类为【活跃性】问题,除了死锁以外,还有活锁和饥饿者两种情 况
活锁
活锁出现在两个线程互相改变对方的结束条件,最后谁也无法结束,可以给一个睡眠的随机数,让两个线程错开执行,这样其中一个线程就无法决定另一个线程的结束条件,从而解决活锁问题.
饥饿
顺序加锁解决死锁问题 ,但容易产生饥饿问题
十三、ReentrantLock (可重入的)
可重入锁
可重入是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁
如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住
static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
method1();
}
public static void method1() {
lock.lock();
try {
log.debug("execute method1");
method2();
} finally {
lock.unlock();
}
}
public static void method2() {
lock.lock();
try {
log.debug("execute method2");
method3();
} finally {
lock.unlock();
}
}
public static void method3() {
lock.lock();
try {
log.debug("execute method3");
} finally {
lock.unlock();
}
}
输出:证明是可重入的
17:59:11.862 [main] c.TestReentrant - execute method1
17:59:11.865 [main] c.TestReentrant - execute method2
17:59:11.865 [main] c.TestReentrant - execute method3
可打断 lock.lockInterruptibly()
可打断是指当前线程等待锁的过程中,其他的线程可以通过 interrupt 方法终止当前线程的等待, synchronized还是ReentrantLock的lock方法也好,都是不可被打断的, 它们是别的线程持有着同步锁,就一直等待下去,反过来,当前线程再等待的时候希望可以终止这种等待可以使用 lock.lockInterruptibly() 方法,这样可以防止没有拿到锁的线程无限制的去等待下去,避免造成死锁的问题.
示例
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
log.debug("启动...");
try {
//没有竞争就会获取锁
//有竞争就进入阻塞队列等待,但可以被打断
lock.lockInterruptibly();
//lock.lock(); //不可打断
} catch (InterruptedException e) {
e.printStackTrace();
log.debug("等锁的过程中被打断");
return;
}
try {
log.debug("获得了锁");
} finally {
lock.unlock();
}
}, "t1");
lock.lock();
log.debug("获得了锁");
t1.start();
try {
sleep(1);
log.debug("执行打断");
t1.interrupt();
} finally {
lock.unlock();
}
输出
18:02:40.520 [main] c.TestInterrupt - 获得了锁
18:02:40.524 [t1] c.TestInterrupt - 启动...
18:02:41.530 [main] c.TestInterrupt - 执行打断
java.lang.InterruptedException
at
java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchr
onizer.java:898)
at
java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchron
izer.java:1222)
at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
at cn.itcast.n4.reentrant.TestInterrupt.lambda$main$0(TestInterrupt.java:17)
at java.lang.Thread.run(Thread.java:748)
18:02:41.532 [t1] c.TestInterrupt - 等锁的过程中被打断
注意如果是不可中断模式,那么即使使用了 interrupt 也不会让等待中断
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
log.debug("启动...");
lock.lock();
try {
log.debug("获得了锁");
} finally {
lock.unlock();
}
}, "t1");
lock.lock();
log.debug("获得了锁");
t1.start();
try {
sleep(1);
log.debug("执行打断");
t1.interrupt();
sleep(1);
} finally {
log.debug("释放了锁");
lock.unlock();
}
输出
18:06:56.261 [main] c.TestInterrupt - 获得了锁
18:06:56.265 [t1] c.TestInterrupt - 启动...
18:06:57.266 [main] c.TestInterrupt - 执行打断 // 这时 t1 并没有被真正打断, 而是仍继续等待锁
18:06:58.267 [main] c.TestInterrupt - 释放了锁
18:06:58.267 [t1] c.TestInterrupt - 获得了锁
(可设置)锁超时 lock.tryLock()🍓
可打断是一种被动的死等,是其他线程调用等待线程的 lockInterruptibly() 来使等待的线程不再进行等待. 锁超时是以一种主动的方式实现避免死等的一种方式。
如果其他线程一直持有者锁没有释放,去尝试获得锁的线程不会一直死等,而是去等待一段时间,如果超过了等待的时间还没有获取到锁,也就是其他的线程还没有释放同步锁,就不会再进行等待了,表示这次获取锁失败,总之还是可以避免线程无限制的等待下去,避免死锁。
lock.tryLock()无参,获取不到锁立刻返回,不会等待
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j(topic = "c.ttestThread14")
public class testThread14 {
@SneakyThrows
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
log.debug("启动...");
if (!lock.tryLock()) {
log.debug("t1获取立刻失败,返回");
return;
}
try {
log.debug("t1获得了锁");
} finally {
lock.unlock();
}
}, "t1");
lock.lock();
log.debug("主线程获得了锁");
t1.start();
try {
Thread.sleep(2000);
} finally {
lock.unlock();
}
}
/**
* 21:20:58 [main] c.ttestThread14 - 主线程获得了锁
* 21:20:58 [t1] c.ttestThread14 - 启动...
* 21:20:58 [t1] c.ttestThread14 - t1获取立刻失败,返回
*/
}
lock.tryLock(TimeOut,TimeUnit) 有参,等待时间一到,没有获得锁立即返回
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j(topic = "c.ttestThread14")
public class testThread14 {
@SneakyThrows
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
log.debug("t1启动...");
try {
if (!lock.tryLock(2, TimeUnit.SECONDS)) {
log.debug("t1等待2秒获取锁失败,返回");
return;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
log.debug("t1获得了锁");
} finally {
lock.unlock();
}
}, "t1");
Thread t2 = new Thread(() -> {
log.debug("t2启动...");
try {
if (!lock.tryLock(2, TimeUnit.SECONDS)) {
log.debug("t2等待2秒获取锁失败,返回");
return;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
log.debug("t2获得了锁");
} finally {
lock.unlock();
}
}, "t2");
lock.lock();
log.debug("主线程获得了锁");
Thread.sleep(1000);
t1.start();
Thread.sleep(500);
log.debug("主线程释放了锁");
lock.unlock();
Thread.sleep(500);
log.debug("主线程又获得了锁");
lock.lock();
Thread.sleep(5000);
t2.start();
}
/**
* 21:33:36 [main] c.ttestThread14 - 主线程获得了锁
* 21:33:37 [t1] c.ttestThread14 - t1启动...
* 21:33:37 [main] c.ttestThread14 - 主线程释放了锁
* 21:33:37 [t1] c.ttestThread14 - t1获得了锁
* 21:33:38 [main] c.ttestThread14 - 主线程又获得了锁
* 21:33:43 [t2] c.ttestThread14 - t2启动...
* 21:33:45 [t2] c.ttestThread14 - t2等待2秒获取锁失败,返回
*/
}
lock.tryLock()解决哲学家就餐问题💕
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.ReentrantLock;
/** 造成死锁
* 19:45:50 [亚里士多德] c.Philosopher - eating...
* 19:45:50 [苏格拉底] c.Philosopher - eating...
* 19:45:51 [亚里士多德] c.Philosopher - eating...
* 19:45:51 [苏格拉底] c.Philosopher - eating...
* 19:45:52 [柏拉图] c.Philosopher - eating...
* 19:45:53 [苏格拉底] c.Philosopher - eating...
*
*/
@Slf4j(topic = "c.testThread13")
public class testThread13 {
public static void main(String[] args) {
Chopstick c1 = new Chopstick("1");
Chopstick c2 = new Chopstick("2");
Chopstick c3 = new Chopstick("3");
Chopstick c4 = new Chopstick("4");
Chopstick c5 = new Chopstick("5");
new Philosopher("苏格拉底", c1, c2).start();
new Philosopher("柏拉图", c2, c3).start();
new Philosopher("亚里士多德", c3, c4).start();
new Philosopher("赫拉克利特", c4, c5).start();
new Philosopher("阿基米德", c5, c1).start();
}
}
//筷子类
class Chopstick extends ReentrantLock{
String name;
public Chopstick(String name) {
this.name = name;
}
@Override
public String toString() {
return "筷子{" + name + '}';
}
}
//哲学家类
@Slf4j(topic = "c.Philosopher")
class Philosopher extends Thread {
Chopstick left;
Chopstick right;
public Philosopher(String name, Chopstick left, Chopstick right) {
super(name);
this.left = left;
this.right = right;
}
private void eat() {
log.debug("eating...");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void run() {
while (true) {
// 获得左手筷子
if(left.tryLock()){
try {
// 获得右手筷子
if(right.tryLock()){
try {
// 吃饭
eat();
}finally {
// 放下右手筷子
right.unlock();
}
}
}finally {
// 放下左手筷子
left.unlock();
}
}
}
}
}
公平锁
公平: 先来就能先执行
不公平: 不保证先来就先执行
公平锁一般没有必要,会降低并发度,后面分析原理时会讲解
条件变量:ReentrantLock的多个waitSet休息室
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j(topic = "c.testThread15")
public class testThread15 {
static ReentrantLock lock = new ReentrantLock();
static Condition waitCigaretteQueue = lock.newCondition();
static Condition waitbreakfastQueue = lock.newCondition();
static volatile boolean hasCigrette = false;
static volatile boolean hasBreakfast = false;
@SneakyThrows
public static void main(String[] args) {
new Thread(() -> {
try {
lock.lock();
while (!hasCigrette) {
try {
waitCigaretteQueue.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("等到了它的烟");
} finally {
lock.unlock();
}
}).start();
new Thread(() -> {
try {
lock.lock();
while (!hasBreakfast) {
try {
waitbreakfastQueue.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("等到了它的早餐");
} finally {
lock.unlock();
}
}).start();
Thread.sleep(1);
sendBreakfast();
Thread.sleep(1);
sendCigarette();
}
private static void sendCigarette() {
lock.lock();
try {
log.debug("送烟来了");
hasCigrette = true;
waitCigaretteQueue.signal();
} finally {
lock.unlock();
}
}
private static void sendBreakfast() {
lock.lock();
try {
log.debug("送早餐来了");
hasBreakfast = true;
waitbreakfastQueue.signal();
} finally {
lock.unlock();
}
}
}
输出
22:22:08 [main] c.testThread15 - 送早餐来了
22:22:08 [Thread-1] c.testThread15 - 等到了它的早餐
22:22:08 [main] c.testThread15 - 送烟来了
22:22:08 [Thread-0] c.testThread15 - 等到了它的烟
同步模式之顺序控制🍕设计模式
固定运行顺序
wait – notify 版
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.testThread16")
public class testThread16 {
static final Object lock = new Object();
static boolean output = false;
@SneakyThrows
public static void main(String[] args) {
Thread t1 = new Thread(()->{
synchronized (lock){
while (!output){
try {
//等待线程 2
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("1");
}
},"T1");
Thread t2 = new Thread(()->{
synchronized (lock){
log.debug("2");
output = true;
//唤醒线程 1
lock.notify();
}
},"T2");
t1.start();
t2.start();
}
}
ReentrantLock – Condition 版
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j(topic = "c.testThread16")
public class testThread16 {
static ReentrantLock lock = new ReentrantLock();
static Condition condition = lock.newCondition();
static boolean output = false;
@SneakyThrows
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(()->{
try {
//上锁
lock.lock();
while (!output){
try {
log.debug("t1进入等待");
condition.await();
}catch (Exception e){
e.printStackTrace();
}
}
log.debug("t1开始打印");
log.debug("1");
}finally {
lock.unlock();
}
},"T1");
Thread t2 = new Thread(()->{
try {
//上锁
lock.lock();
output = true;
log.debug("2");
condition.signal();
}finally {
//释放锁给 t1 使用
lock.unlock();
}
},"T2");
t1.start();
t2.start();
}
}
输出
23:15:57 [T1] c.testThread16 - t1进入等待
23:15:57 [T2] c.testThread16 - 2
23:15:57 [T1] c.testThread16 - t1开始打印
23:15:57 [T1] c.testThread16 - 1
park – Unpark 版
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.LockSupport;
@Slf4j(topic = "c.testThread17")
public class testThread17 {
public static void main(String[] args) {
Thread t1 = new Thread(()->{
LockSupport.park();
log.debug("1");
},"T1");
t1.start();
new Thread(()->{
log.debug("2");
LockSupport.unpark(t1);
},"T2").start();
}
}
输出
23:20:52 [T2] c.testThread17 - 2
23:20:52 [T1] c.testThread17 - 1
交替输出
wait – notify 版
代码示例
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.LockSupport;
@Slf4j(topic = "c.testThread17")
public class testThread17 {
public static void main(String[] args) {
/**输出
* abcabcabcabcabcabcabcabcabcabc
* 输出内容 等待标记 下一个标记
* a 1 2
* b 2 3
* c 3 1
* 三个变量没办法用布尔值表示
*/
RePrint rePrint = new RePrint(1,10);
new Thread(()->{
rePrint.print("a",1,2);
}).start();
new Thread(()->{
rePrint.print("b",2,3);
}).start();
new Thread(()->{
rePrint.print("c",3,1);
}).start();
}
}
class RePrint{
//初始标记
private int initial;
//循环打印次数
private int num;
public RePrint(int initial, int num) {
this.initial = initial;
this.num = num;
}
//打印方法
public void print(String str,int sign , int next){
//循环打印次数
for (int i = 0;i < num;i++){
//加同步锁
synchronized (this){
while (initial != sign){
try {
//初始标记和线程标记不一样就等待
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print(str);
initial = next;
//唤醒所有线程进行下一轮判断
notifyAll();
}
}
}
}
ReentrantLock – Condition 版
示例
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.LockSupport;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j(topic = "c.testThread17")
public class testThread17 {
@SneakyThrows
public static void main(String[] args) {
/**输出
* abcabcabcabcabcabcabcabcabcabc
* 输出内容 当前休息室 下一个线程的休息室
* a a b
* b b c
* c c a
* 三个变量没办法用布尔值表示
*/
RePrint rePrint = new RePrint(10);
Condition a = rePrint.newCondition();
Condition b = rePrint.newCondition();
Condition c = rePrint.newCondition();
new Thread(()->{
rePrint.print("a",a,b);
}).start();
new Thread(()->{
rePrint.print("b",b,c);
}).start();
new Thread(()->{
rePrint.print("c",c,a);
}).start();
Thread.sleep(1000);
try {
//让主线程上锁才能调用 signal 方法
rePrint.lock();
a.signal();
}finally {
rePrint.unlock();
}
}
}
class RePrint extends ReentrantLock{
//循环打印次数
private int num;
public RePrint(int num) {
this.num = num;
}
//打印方法
@SneakyThrows
public void print(String str, Condition sign , Condition next){
//循环打印次数
for (int i = 0;i < num;i++){
//加同步锁
lock();
try {
//让当前线程进入休息室
sign.await();
//输出
System.out.print(str);
//唤醒下一间休息室的线程
next.signal();
}finally {
unlock();
}
}
}
}
park – Unpark 版
示例
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.LockSupport;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j(topic = "c.testThread17")
public class testThread17 {
static Thread t1;
static Thread t2;
static Thread t3;
@SneakyThrows
public static void main(String[] args) {
/**输出
* abcabcabcabcabcabcabcabcabcabc
*/
RePrint rePrint = new RePrint(10);
t1 = new Thread(()->{
rePrint.print("a",t2);
});
t2 = new Thread(()->{
rePrint.print("b",t3);
});
t3 = new Thread(()->{
rePrint.print("c",t1);
});
t1.start();
t2.start();
t3.start();
//唤醒第一个线程开始循环
LockSupport.unpark(t1);
}
}
class RePrint{
//打印方法
@SneakyThrows
public void print(String str, Thread next){
//循环打印次数
for (int i = 0;i < num;i++){
//暂停当前线程
LockSupport.park();
System.out.print(str);
//唤醒下一个线程
LockSupport.unpark(next);
}
}
//循环打印次数
private int num;
public RePrint(int num) {
this.num = num;
}
}