相关源码:boy-learning-thread
个人博客:http://bruce.bugmakers.club
内容来自《网易微专业 - 高性能编程章节》
线程通信
通信方式
要想实现多个线程之间的协同,如:线程执行先后顺序、获取某个线程的执行结果等待。
涉及到线程时间的相互通信,分为下面四类:
- 文件共享
- 网络共享
- 共享变量
- jdk 提供的线程协调 API,细分为:suspend/resume、wait/notify、park/unpark
1、文件共享
// 共享文件
File file = new File("a.txt");
thread1 写文件
thread2 读文件
2、变量共享
// 共享变量
public static String content = "init-txt";
thread1 写入内存变量
thread2 读取内存变量
3、网络共享
4、线程协作 - JDK API
JDK 中对于需要多线程协作完成某一任务的场景,提供了对应的API支持。
多线程协作的典型场景是:生产者 - 消费者模型。(线程阻塞、线程唤醒)
场景示例
线程1去买包子,没有包子,则不再执行。 线程2生产包子,通知线程1继续执行。
4.1、suspend/resume 机制
thread.suspend() // 挂起
thread.resume // 激活
suspend/resume 这种组合已经被 jdk 遗弃掉了,原因是容易出现死锁。
死锁场景一:同步代码块中
Object baozi = null;
Thread consumer = new Thread(() -> {
if (baozi == null) {
System.out.println("来晚了,卖光了");
synchronized(this) {
Thread.currentThread().suspend();
}
}
System.out.println("买到包子,回家");
});
// 包子制作中
Thread.sleep(3000L);
baozi = new Object();
System.out.println("新一笼的包子做好啦");
// 争取到锁后,在恢复 consumer
synchronized(this) {
consumer.resume();
}
死锁场景二:先后顺序没有控制好
Object baozi = null;
Thread consumer = new Thread(() -> {
if (baozi == null) {
System.out.println("来晚了,卖光了");
Thread.sleep(5000L);
Thread.currentThread().suspend();
}
System.out.println("买到包子,回家");
});
// 包子制作中
Thread.sleep(3000L);
baozi = new Object();
System.out.println("新一笼的包子做好啦");
consumer.resume();
consumer.join();
4.2、wait/notify 机制
synchronized(LOCK) {
Object.wait(); // wait会释放LOCK对象锁
}
synchronized(LOCK) {
Object.notify();
}
这些方法只能由同一对象锁的持有者线程调用,也就是写在同步代码块里边,否则会抛出 IllegalMonitorStateException 异常。
wait 方法导致当前线程等待,加入该对象的等待集合中,并且放弃当前持有的对象锁。
notify/notifyAll 方法唤醒一个或所有正在等待这个对象锁的线程。
注意:虽然会 wait 自动解锁,但是对顺序有要求,如果在 notify 被调用之后,才开始 wait 方法的调用,线程会永远处于 WAITING 状态。
4.3、park/unpark 机制
LockSupport.park();
LockSupport.unpark();
线程调用 park 则等待 “许可”,unpark 方法为指定线程提供 “许可(permit)”。
不要求 park/unpark 的调用顺序。
多次调用 unpark 后,再调用 park,线程会直接运行。
但不会叠加,也就是说,连续多次调用 park 方法,第一次会拿到 “许可” 直接运行,后续调用会进入等待。
LockSupport 不会释放持有的对象锁,所以在同步代码块中,会出现死锁的现象。
死锁场景:
synchronized(LOCK) {
LockSupport.park();
}
//...
synchronized(LOCK) {
LockSuport.unpark();
}
伪唤醒
警告!之前代码中用 if 语句来判断是否进入等待状态,是错误的!
官方建议,应该在循环中检查等待条件,原因是处于等待状态的线程可能会收到错误报警和伪唤醒,如果不在循环中检查等待条件,程序就会在没有满足结束条件的情况下退出。
伪唤醒是指线程并非因为 notify、notifyAll、unpark 等 api 调用唤醒,是更底层原因导致的。