线程通信
想要实现多个线程之间的协同,如:线程执行顺序,获取某个线程执行结果等,则需要使用线程之间互相通信。
文件共享
通过多个线程对同一文件操作的通信方式网络共享
如socket通信,双向的通信连接实现数据的交换共享变量
对同一内存或缓存中的变量进行数据操作jdk提供的线程协调API
jdk中对于需要多线程写作完成的任务场景,提供API支持,例如:生成者 - 消费者 (线程阻塞、线程唤醒)
细分为:suspend/resume、 wait/notify、 park/unpark
线程协调API
-
suspend和resume API弃用
suspend:挂起目标线程
resume:恢复线程执行
弃用原因:容易出现死锁代码- 在使用synchronized 同步锁的情况下,两个线程一个锁住,另一个线程无法拿到锁,造成死锁
2.resume(恢复)先执行,suspend(挂起)后执行,将会永远处于Waiting状态,出现死锁
/** 正常的suspend/resume */
public void suspendResumeTest() throws Exception {
// 启动线程
Thread consumerThread = new Thread(() -> {
if (baozidian == null) { // 如果没包子,则进入等待
System.out.println("1、进入等待");
Thread.currentThread().suspend();
}
System.out.println("2、买到包子,回家");
});
consumerThread.start();
// 3秒之后,生产一个包子
Thread.sleep(3000L);
baozidian = new Object();
consumerThread.resume();
System.out.println("3、通知消费者");
}
/** 死锁的suspend/resume。 suspend并不会像wait一样释放锁,故此容易写出死锁代码 */
public void suspendResumeDeadLockTest() throws Exception {
// 启动线程
Thread consumerThread = new Thread(() -> {
if (baozidian == null) { // 如果没包子,则进入等待
System.out.println("1、进入等待");
// 当前线程拿到锁,然后挂起
synchronized (this) {
Thread.currentThread().suspend();
}
}
System.out.println("2、买到包子,回家");
});
consumerThread.start();
// 3秒之后,生产一个包子
Thread.sleep(3000L);
baozidian = new Object();
// 争取到锁以后,再恢复consumerThread
synchronized (this) {
consumerThread.resume();
}
System.out.println("3、通知消费者");
}
/** 导致程序永久挂起的suspend/resume */
public void suspendResumeDeadLockTest2() throws Exception {
// 启动线程
Thread consumerThread = new Thread(() -> {
if (baozidian == null) {
System.out.println("1、没包子,进入等待");
try { // 为这个线程加上一点延时
Thread.sleep(5000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 这里的挂起执行在resume后面
Thread.currentThread().suspend();
}
System.out.println("2、买到包子,回家");
});
consumerThread.start();
// 3秒之后,生产一个包子
Thread.sleep(3000L);
baozidian = new Object();
consumerThread.resume();
System.out.println("3、通知消费者");
consumerThread.join();
}
-
wait和notify
只能由同一对象锁的持有者线程调用,并且必须在synchronized块里面,否则会抛出 IllegalMonitorStateExceptionwait: 当前线程进入等待,加入对象等待集合中,并且释放持有对象锁
notify/notifyAll:唤醒一个或多个正在等待这个对象锁的线程
死锁:虽然wait会自动解锁,但是对顺序有要去,如果在notify后执行,线程将永远处于Waiting状态
/** 正常的wait/notify */
public void waitNotifyTest() throws Exception {
// 启动线程
new Thread(() -> {
synchronized (this) {
while (baozidian == null) { // 如果没包子,则进入等待
try {
System.out.println("1、进入等待");
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println("2、买到包子,回家");
}).start();
// 3秒之后,生产一个包子
Thread.sleep(3000L);
baozidian = new Object();
synchronized (this) {
this.notifyAll();
System.out.println("3、通知消费者");
}
}
/** 会导致程序永久等待的wait/notify */
public void waitNotifyDeadLockTest() throws Exception {
// 启动线程
new Thread(() -> {
if (baozidian == null) { // 如果没包子,则进入等待
try {
Thread.sleep(5000L);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
synchronized (this) {
try {
System.out.println("1、进入等待");
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println("2、买到包子,回家");
}).start();
// 3秒之后,生产一个包子
Thread.sleep(3000L);
baozidian = new Object();
synchronized (this) {
this.notifyAll();
System.out.println("3、通知消费者");
}
}
-
park/unpark
采用等待“许可/令牌” 与提供“许可/令牌” 的形式来激活线程,对执行顺序无要求park:获取或等待“许可“,拿到“许可“则直接跳过等待,多次调用第二次会需要等待
unpark:为线程提供“许可”,多次调用只产生一条,不会叠加
死锁:LockSupport.park()在同步代码块中,不会释放锁,其他线程使用该同步锁的情况下,会永远拿不到锁
/** 正常的park/unpark */
public void parkUnparkTest() throws Exception {
// 启动线程
Thread consumerThread = new Thread(() -> {
while (baozidian == null) { // 如果没包子,则进入等待
System.out.println("1、进入等待");
LockSupport.park();
}
System.out.println("2、买到包子,回家");
});
consumerThread.start();
// 3秒之后,生产一个包子
Thread.sleep(3000L);
baozidian = new Object();
LockSupport.unpark(consumerThread);
System.out.println("3、通知消费者");
}
/** 死锁的park/unpark */
public void parkUnparkDeadLockTest() throws Exception {
// 启动线程
Thread consumerThread = new Thread(() -> {
if (baozidian == null) { // 如果没包子,则进入等待
System.out.println("1、进入等待");
// 当前线程拿到锁,然后挂起
synchronized (this) {
LockSupport.park();
}
}
System.out.println("2、买到包子,回家");
});
consumerThread.start();
// 3秒之后,生产一个包子
Thread.sleep(3000L);
baozidian = new Object();
// 争取到锁以后,再恢复consumerThread
synchronized (this) {
LockSupport.unpark(consumerThread);
}
System.out.println("3、通知消费者");
}
注意事项 - 伪唤醒
伪唤醒
指线程并非因notify、notifyall、unpark等api调用而唤醒,是更底层的原样导致的。在线程代码中使用if语句来判断是否进入等待状态,是错误的
官方建议应该在循环中检查等待条件,原因是处于等待状态的线程可能会收到错误的报警和伪唤醒,导致程序没有满足条件的情况下自动唤醒
正确的条件判断 ,使用循环检查条件
// wait
synchronized(obj){
while(<条件判断>){
obj.wait();
...//执行后续操作
}
}
// park
while(<条件判断>){
LockSupport.park();
...//执行后续操作
}