有的时候我们希望线程按照希望的顺序依次执行,比如线程A,B,C,按照顺序依次执行,这时候就要用到阻塞和唤醒,之前的时候我们学到过wait()
和nofity/notifyAll()
这两个方法,这里我们使用java.concurrent.locks.Lock
接口来实现类似的功能;
用到的包和类
java.concurrent.locks.Lock:接口
|-->java.concurrent.locks.ReentrantLock:实现类
|-->java.util.concurrent.locks.Condition:抽象类
方法:
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
要求
- 创建一个TestAlternate类,有三个方法loopA(),loopB(),loopC(),分别打印A,B,C
- 主函数中创建三个线程,绑定三个匿名类实现Runnable接口
- 主函数中循环10次,使得每次打印都按照A–>B–>C的顺序来打印
创建类
TestAlternate.java
class TestAlternate{
//线程执行顺序标记,1:表示loopA执行,2:表示loopB执行,3:表示loopC执行
private volatile int number = 1;
//获得lock锁
private Lock lock = new ReentrantLock();
//创建三个condition对象用来await(阻塞)和signal(唤醒)指定的线程
private Condition c1 = lock.newCondition();
private Condition c2 = lock.newCondition();
private Condition c3 = lock.newCondition();
protected void loopA(){
lock.lock();//上锁
try {
/*如果不是第一个标志位,就阻塞,为了解决虚假唤醒问题,使用while关键字
*/
while(number!=1){
try {
c1.await();//阻塞类似wait()
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"-A");
number = 2;//使能第二个方法
c2.signal();//唤醒第二个线程,类似notify()方法
} finally {
lock.unlock();//解锁
}
}
protected void loopB(){
lock.lock();//上锁
try {
//如果不是第一个标志位,就阻塞
while(number!=2){
try {
c2.await();//阻塞类似wait()
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"-B");
number = 3;//使能第3个方法
c3.signal();//唤醒第三个线程,类似notify()方法
} finally {
lock.unlock();//解锁
}
}
protected void loopC(){
lock.lock();//上锁
try {
//如果不是第一个标志位,就阻塞
while(number!=3){
try {
c3.await();//阻塞类似wait()
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"-C");
number = 1;//使能第1个方法
c1.signal();//唤醒第一个线程,类似notify()方法
} finally {
lock.unlock();//解锁
}
}
}
测试输出:
loopA0-A
loopB0-B
loopC0-C
loopA1-A
loopB1-B
loopC1-C
loopC2-C//虚假唤醒问题
loopA2-A
loopB2-B
虚假唤醒的注意事项
出现虚假唤醒的原因:
假如A1,A2两个线程争夺loopA,A2夺得了cpu执行权,结果发现此时A2的标记为number不是1,于是await,A2开始阻塞这个时候释放锁和资源,然后B,C线程得到cpu执行权按照顺序执行完毕,此时A的标志位是1,此时A1和A2的锁都是c2.await()A1,A2同时被被唤醒,A1抢到了cpu执行权,打印输出loopA,并改变number为2,然后由于A2也被唤醒,但是由于是if
语句,在阻塞前只判断了一次,即便此时number不是2了,但是A2不会再次判断number的值,继续往下执行,导致重复输出loopA
。
解决方案:
把if
替换为while
,使得每次都判断number的值是否正确,保证了程序的正常运行,避免虚假唤醒的情况出现。