线程通信是指多个线程之间的协同,如:线程执行先后顺序、获取某个线程的执行结果等。涉及到线程之间相互通信,分为四类:文件共享、网络共享、变量共享、JDK提供的线程协调API。
线程协调API
通过多线程协助的典型场景:生产者-消费者模型,来说明。
1、被弃用的suspend/resume
由于suspend()执行后不会释放锁,导致resume()永远无法拿到锁,而无法通知线程继续执行,示例代码:
public void suspendResumeDeadLockTest() throws Exception{
baozi = null;
Thread consumerThread = new Thread(() -> {
while(baozi == null){
System.out.println("没包子,进入等待...");
synchronized (this) {
Thread.currentThread().suspend();
}
}
System.out.println("买到包子,回家...");
});
// 开始线程
consumerThread.start();
// 3秒后,生产包子
Thread.sleep(3000);
baozi = new Object();
synchronized (this) {
consumerThread.resume();
}
System.out.println("通知消费者");
}
输出
没包子,进入等待...
由于suspend/resume对执行顺序有强制的先执行suspend()后执行resume(),否则线程无法正常执行,示例代码:
public void suspendResumeDeadLockTest2() throws Exception{
baozi = null;
Thread consumerThread = new Thread(() -> {
while(baozi == null){
try {
Thread.sleep(5000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("没包子,进入等待...");
Thread.currentThread().suspend();
}
System.out.println("买到包子,回家...");
});
// 开始线程
consumerThread.start();
// 3秒后,生产包子
Thread.sleep(3000);
baozi = new Object();
consumerThread.resume();
System.out.println("通知消费者");
}
输出
通知消费者
没包子,进入等待...
2、wait/notify
wait/notify必须在同步代码快中执行,wait()执行后,会释放拿到的锁,不会导致notify()执行时拿不到锁的情况,示例代码:
public void waitNotifyTest() throws Exception{
baozi = null;
Thread consumerThread = new Thread(() -> {
while(baozi == null){
System.out.println("没包子,进入等待...");
synchronized (this) {
try {
this.wait();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
System.out.println("买到包子,回家...");
});
// 开始线程
consumerThread.start();
// 3秒后,生产包子
Thread.sleep(3000);
baozi = new Object();
synchronized (this) {
this.notify();
}
System.out.println("通知消费者");
}
输出
没包子,进入等待...
通知消费者
买到包子,回家...
wait/notify依然对执行顺序有严格的要求,如果在wait()前执行notify(),那么线程会永远处于Waiting状态,示例代码:
public void waitNotifyLockTest() throws Exception{
baozi = null;
Thread consumerThread = new Thread(() -> {
while(baozi == null){
System.out.println("没包子,进入等待...");
try {
Thread.sleep(5000);
} catch (Exception e1) {
e1.printStackTrace();
}
synchronized (this) {
try {
this.wait();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println("买到包子,回家...");
}
});
// 开始线程
consumerThread.start();
// 3秒后,生产包子
Thread.sleep(3000);
baozi = new Object();
synchronized (this) {
this.notify();
}
System.out.println("通知消费者");
}
输出
没包子,进入等待...
通知消费者
3、park/unPark
park/unPark对执行顺序没有要求,unPark()方法会对执行线程放出一个许可,当线程执行park()后,会一直查看是否有许可,获取到许可会继续执行,多次调用unPark()后,在调用park(),程序会直接执行。UNPark()的效果不会叠加。但是在同步代码块中使用时,park()方法无法释放锁。
正常执行的代码示例:
public void parkUnParkTest() throws Exception{
baozi = null;
Thread consumerThread = new Thread(() -> {
while(baozi == null){
System.out.println("没包子,进入等待...");
try {
Thread.sleep(5000);
} catch (Exception e1) {
e1.printStackTrace();
}
LockSupport.park();
System.out.println("已经收到有包子的通知,墨迹一会儿...");
LockSupport.park();
System.out.println("买到包子,回家...");
}
});
// 开始线程
consumerThread.start();
// 3秒后,生产包子
Thread.sleep(3000);
baozi = new Object();
System.out.println("通知消费者...");
LockSupport.unpark(consumerThread);
LockSupport.unpark(consumerThread);
LockSupport.unpark(consumerThread);
Thread.sleep(7000);
System.out.println("通知消费者不买包子就离开...");
LockSupport.unpark(consumerThread);
}
输出
没包子,进入等待...
通知消费者...
已经收到有包子的通知,墨迹一会儿...
通知消费者不买包子就离开...
买到包子,回家...
无法释放锁的代码示例:
public void parkUnParkBlockTest() throws Exception{
baozi = null;
Thread consumerThread = new Thread(() -> {
while(baozi == null){
System.out.println("没包子,进入等待...");
try {
//Thread.sleep(5000);
} catch (Exception e1) {
e1.printStackTrace();
}
synchronized (this) {
LockSupport.park();
}
System.out.println("买到包子,回家...");
}
});
// 开始线程
consumerThread.start();
// 3秒后,生产包子
Thread.sleep(3000);
baozi = new Object();
synchronized (this) {
System.out.println("通知消费者...");
LockSupport.unpark(consumerThread);
}
}
输出
没包子,进入等待...
在同步代码块中park()执行后不会释放锁,但可先执行unPark(),先给线程一个许可,这个需要结合实际业务来看。
代码中使用while代替if进行判断,是为了避免伪唤醒。
文件共享
线程A写入数据到文件,其他线程可以读取到线程A写入的数据。
网络共享
与文件共享类似。
变量共享
利用内存,线程A写入到内存的一个变量中,其他线程可以读取。