线程通信
synchronized 和Lock 的线程通信
-
关键字
synchronized
与wait()
/notify()
这两个方法一起使用可以实现等待/通 知模式用
notify()
通知时,JVM
会随机唤醒某个等待的线程。注意:如果有多个线程,并且设置了状态锁的时候使用notify()
时会造成死锁问题,此时应使用notifyAll()
- wait()会使当前线程等待,同时会释放锁,直到被唤醒,便从当前位置继续执行。 - notify()用于随机唤醒一个等待的线程,从被锁主的地方继续执行。 - notifyAll()用于唤醒全部等待的线程。
下面的代码在使用2个线程以上分别执行
product()
和consume()
的时候便可能会进如死锁。/** *生产 */ public synchronized void product() throws InterruptedException { //修复虚假通知 while (productCount != 0) { wait(); } //通知消费 productCount++; notify(); } /** *消费 */ public synchronized void consume() throws InterruptedException { //修复虚假通知 while (productCount != 1) { wait(); } // 通知生产 productCount--; notify(); }
这里调用的时候可能会
AA
线程生产之后notify()
的唤醒通知发到了BB
线程,而BB
线程满足等待条件productCount != 0
又会进行等待,没有现成进行消费所以就进入了死锁。解决:将notify()换成notifyAll()即可new Thread(()->product(),"AA").start(); // 生产方法 new Thread(()->product(),"BB").start(); // 生产方法 new Thread(()->consume()},"CC").start(); // 消费方法 new Thread(()->consume()},"DD").start(); // 消费方法
-
Lock
锁的newContition()
方法返回Condition
对象,Condition
类 也可以实现等待/通知模式在调用
Condition
的await()
/signal()
/signalAll()
方法前,也需要线程持有相关的 Lock 锁- await()会使当前线程等待,同时会释放锁,直到被唤醒,便从当前位置继续执行。 - signal()用于唤醒一个等待的线程,不是随机。 - signalAll()用于唤醒全部等待的线程。
private Lock lock = new ReentrantLock(); private Condition condition1 = lock.newCondition(); /** * 生产 */ public void product() throws InterruptedException { lock.lock(); try { if (productCount != 0) { //达到仓库上限等待消费处理 condition1.await(); } //通知消费 productCount++; condition1.signalAll(); } finally { lock.unlock(); } } /** * 消费 */ public void consume() throws InterruptedException { lock.lock(); try { if (productCount != 1) { //仓库产品不足等待生产 condition1.await(); } // 通知生产 productCount--; condition1.signalAll(); } finally { lock.unlock(); } }
这里的唤醒机制存在虚假唤醒不能保证线程一直让
productCount
处于1 / 0交替,因为在AA
线程进行signalAll()
时可能会被BB
线程抢到并执行这时候便会进行生产操作,而再次signalAll()
时才可能被消费方法抢到并执行。解决:将if()
判断换成while()
进行循环判断new Thread(()->product(),"AA").start(); // 生产方法 new Thread(()->product(),"BB").start(); // 生产方法 new Thread(()->consume()},"CC").start(); // 消费方法 new Thread(()->consume()},"DD").start(); // 消费方法
线程定制化通信
线程定制化通信需要添加一个标志位确定需要执行哪一个方法 下列代码中使用了tag作为标志位。
0的时候生产,1的时候consume消费,2的时候conusme1消费。
资源类
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
/**
* 标志
*/
private Integer tag = 0;
/**
* 生产
*/
@Override
public void product() throws InterruptedException {
lock.lock();
try {
while (tag !=0) {
//达到仓库上限等待消费处理
condition1.await();
}
//通知消费
productCount += 2;
tag++;
System.out.println(Thread.currentThread().getName() + "线程生产产品+++++++++++++++++++++++++++++++++++++++++++" + productCount);
condition2.signal();
} finally {
lock.unlock();
}
}
/**
* 消费
*/
@Override
public void consume() throws InterruptedException {
lock.lock();
try {
while (tag!=1) {
//仓库产品不足等待生产
condition2.await();
}
// 通知生产
productCount--;
tag++;
System.out.println(Thread.currentThread().getName() + "线程消费产品-----------------------consume--------------------" + productCount);
condition3.signal();
} finally {
lock.unlock();
}
}
public void consume1() throws InterruptedException {
lock.lock();
try {
while (tag!=2) {
//仓库产品不足等待生产
condition3.await();
}
// 通知生产
productCount--;
tag = 0;
System.out.println(Thread.currentThread().getName() + "线程消费产品-------------------consume1------------------------" + productCount);
condition1.signal();
} finally {
lock.unlock();
}
}
执行代码
new Thread(()->{
for (int i = 0; i < 20; i++) {
try {
factory.consume();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"BB").start();
new Thread(()->{
for (int i = 0; i < 20; i++) {
try {
factory.product();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"AA").start();
new Thread(()->{
for (int i = 0; i < 20; i++) {
try {
factory.consume1();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"CC").start();
效果
AA线程生产产品+++++++++++++++++++++++++++++++++++++++++++2
BB线程消费产品-----------------------consume--------------------1
CC线程消费产品-------------------consume1------------------------0
AA线程生产产品+++++++++++++++++++++++++++++++++++++++++++2
BB线程消费产品-----------------------consume--------------------1
CC线程消费产品-------------------consume1------------------------0
隐患
当其中一个定制化通信的方法出现异常或是出现问题无法修改标志位的时候则整个线程就无法进行下去
假设AA线程在执行中无法将tag修改为1时接下来的线程将无法进行下去,将存在死锁现象,其他线程将持续等await()
处理方法1:添加线程等待时间和触发条件进行强制执行(会出意外,不能一定能按照逻辑执行不推荐)
while (tag !=0) {
// 达到仓库上限等待消费处理,设置超时时间1s 超过等待时间则返回false
if (!condition1.await(1, TimeUnit.SECONDS)) {
// 检查是否超时
// 在超时后检查tag的状态,如果不满足预期,则处理
if (tag != 0) {
// 处理不满足预期的情况,比如抛出异常或者打印错误日志
System.err.println("Product timed out waiting for tag to be 0.");
}
// 退出当前方法
return;
}
}
处理方法2:添加线程等待时间终止线程
while (tag != 0) {
// 达到仓库上限等待消费处理,设置超时时间
if (!condition1.await(1, TimeUnit.SECONDS)) {
// 处理不满足预期的情况,比如抛出异常或者打印错误日志
System.err.println("线程" + Thread.currentThread().getName() + "线程嘎调了");
//终止执行
Thread.currentThread().interrupt();
}
}