一、synchronized和wait和notify的使用
这里为了让程序交替输出1和0,代码如下:
package com.test.notify;
public class TestNotify {
private int count = 0;
public synchronized void increase() {
while (count != 0) { // 不能使用 if (count != 0)
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count++;
System.out.print(count);
notify();
}
public synchronized void decrease() {
while (count == 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count--;
System.out.print(count);
notify();
}
public static void main(String[] args) {
TestNotify testNotify = new TestNotify();
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
testNotify.increase();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
testNotify.decrease();
}
}).start();
}
}
}
执行程序发现,有时候程序可以执行圆满结束,如:
分析:为什么这段程序有时会存在不能被唤醒的线程?
为了思考起来比较简单,我们在程序里不设置10次循环了,这里假设仅设定循环2次,也就是创建了两个加1线程和两个减1线程。为了程序分析起来方便,我们分别给:
两个加线程命名为线程1、线程2;
两个减线程命名为线程3、线程4。
我们假设程序在运行时恰好是这样一种情况:
- 1、线程3进入decrease方法,此时count值为0,判断count==0成立,进入while循环中并执行wait方法,此时线程1释放掉锁并被阻塞住
- 2、线程4通过锁竞争也进入到了decrease方法,此时count值仍为0,判断count==0成立,进入while循环中并执行wait方法,此时线程4也释放掉锁并被阻塞住
- 3、线程1进入increase方法,判断count!=0不成立,故而进行count++操作,count此时值变为了1,执行打印,并执行notify方法,线程1结束了(线程1彻底Game Over了,从此以后再也没线程1什么事了...)
- 4、回到第三步骤中,线程1在死亡之前执行了notify方法,这个notify会唤醒线程3或者线程4中的一个
- 5、这里假设线程1唤醒的是线程3,线程3准备从wait处继续执行,然后线程3和还没干什么事儿的线程2同时竞争锁,结果线程3竞争锁失败,只是进入了就绪状态,并没有被cpu调度执行
- 6、线程2进入increase方法,此时count值为1,判断count!=0成立,进入while循环中并执行wait方法,此时线程2释放掉锁并被阻塞住
- 7、线程2释放锁以后,线程4还处于被阻塞的状态,线程3顺理成章拿到锁接着执行,继续while循环判断条件,此时count值为1,判断count==0不成立,故而进行count--操作,count此时值变为了0,执行打印,并执行notify方法,线程3结束了(线程3也彻底Game Over了,从此以后再也没线程3什么事了...)
- 8、此时只剩下线程2和线程4了
- 9、回到第7步骤中,线程3在死亡之前执行了notify方法,这个notify会唤醒线程2和线程4中的一个,线程2是加线程,线程4是减线程
- 10、这里假设线程3唤醒的是线程4,也就是减线程,接着wait方法执行,继续判断while条件,此时count值为0,判断count==0成立,又进入到了while循环中并执行了wait方法,此时线程4又释放掉锁并被阻塞住!
- 11、线程2不用管,还在阻塞着呢!
- 12、程序从此结束不了了,线程2和线程4从此一直被阻塞了下去....
分析得出,程序之所以阻塞,是因为在唤醒的时候,唤醒了执行同类方法的线程(也就是减线程,恰好唤醒的是其他减线程),而没有唤醒需要唤醒的线程(加线程)。 所以我们引出了Condition类。
二、Condition的使用
Lock接口里有一个成员变量,是Condition类的实例。之所以引出这个Condition类,是因为Condition类提供了和wait、notify一样的功能。这里的区别是:notify在进行唤醒的时候,只有一个等待池(调用wait方法以后,线程会进入一个叫waitset的集合中,等待被notify唤醒);而与Lock实例所关联的condition实例,可以有多个,每个condition实例可以想象成类似waitset这样的集合,我们可以分别把不同的业务线程,通过await方法放入到不一样的condition当中,在唤醒时,调用不同condition实例的signal方法,来唤醒业务不同的线程。以下是代码:
package com.test.condition;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TestCondition {
public int count = 0;
public Lock lock = new ReentrantLock();
public Condition increaseCondition = lock.newCondition();
public Condition decreaseCondition = lock.newCondition();
public synchronized void increase() {
lock.lock();
try {
while (count != 0) { // 不能使用 if (count != 0)
try {
increaseCondition.await(); // 加1线程阻塞
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count++;
System.out.print(count);
decreaseCondition.signal(); // 唤醒减1线程
} finally {
lock.unlock();
}
}
public void decrease() {
lock.lock();
try {
while (count == 0) { // 不能使用 if (count != 0)
try {
decreaseCondition.await(); // 减1线程阻塞
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count--;
System.out.print(count);
increaseCondition.signal(); // 唤醒加1线程
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
TestCondition testCondition = new TestCondition();
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
testCondition.increase();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
testCondition.decrease();
}
}).start();
}
}
}
执行结果:
减线程唤醒时,唤醒的是增线程; 增线程在唤醒时,唤醒的是减线程! 不会再发生notify唤醒同业务线程的尴尬情况了(不会再导致有的线程被阻塞住而不能被运行了)