1. 关于线程同步问题
1.1 实现功能
实现两个线程交替打印数值,使用lock实现,效果如下:
线程1打印1
线程2打印2
线程1打印3
线程2打印4
…
1.2 思路分析
- 创建打印数值的任务
- 使用lock同步该任务
- 创建两个线程并启动
- 当前线程执行打印任务之后,就进入阻塞队列,同时通知另一个线程执行打印任务
- 循环执行
1.3 第一版代码
public class Test {
private static int num=0;//初始数值
private static Lock lock=new ReentrantLock();//创建锁
private static Condition condition=lock.newCondition();//创建一个阻塞队列
//程序入口
public static void main(String[] args) {
//创建两个线程
for(int i=1;i<3;i++){
new Thread(new Runnable() {
@Override
public void run() {
//循环执行
while(true){
//打印数值
Test.add();
}
}
}, "" + i).start();
}
}
//打印任务
public static void add(){
lock.lock();//上锁
try {
//获取当前线程名
String name=Thread.currentThread().getName();
num++;//方法调用一次,数值增加一次
//打印数值
System.out.println("线程"+name+"打印"+num);
//打印后进入阻塞状态
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}finally {
condition.signalAll();//唤醒阻塞队列的所有等待线程
lock.unlock();//解锁
}
}
}
1.4 第一版代码执行结果及分析
如图显示,打印数值的任务只执行了两次,且程序一直没有终止,处于等待状态,那么就只有一种可能,那就是线程1打印了1之后进入了阻塞队列,然后线程2打印了2也进入了阻塞队列,但线程2并没有唤醒线程1,所以结果是线程1和线程2都在等待队列,程序也就一直等待。
那么为什么会这样呢?不是在finally调用了signalAll方法吗?怎么线程没有被唤醒?难道signalAll方法没被调用吗?
1.5 验证signalAll
要验证也很简单,直接在signalAll之前放一句打印语句,如果输出了,那么就执行了,如果没输出,那就没执行
public static void add(){
lock.lock();//上锁
try {
//获取当前线程名
String name=Thread.currentThread().getName();
num++;//方法调用一次,数值增加一次
//打印数值
System.out.println("线程"+name+"打印"+num);
//打印后进入阻塞状态
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}finally {
System.out.println("你到底执没执行?");
condition.signalAll();//唤醒阻塞队列的所有等待线程
lock.unlock();//解锁
}
}
结果
很明显了,没有执行
那又有新问题了!
在我的记忆中,记得老师讲过try里面即使发生了异常,finally语句也会执行啊,但这里并没有执行!!!
另外,如果finally没执行,那就也没法执行lock.unlock(),锁就解不了,那线程2怎么拿到锁的呢???
1.6 真相只有一个
仔细推理就会发现,问题出在condition.await()
为什么这么说呢?
因为,现在两个线程都堵塞了,怎么才能唤醒呢?其实只要在执行condition.await()之前执行signalAll()就行了
也就是,在让一个线程阻塞之前,先唤醒两个线程,然后不就执行一个线程的阻塞了吗?但这时候另一个线程是就绪的,当执行这个就绪的线程时,又会唤醒开始阻塞的那个线程,然后自己再阻塞,循环如此,问题就解决了,功能也就实现了
public static void add(){
lock.lock();//上锁
try {
//获取当前线程名
String name=Thread.currentThread().getName();
num++;//方法调用一次,数值增加一次
//打印数值
System.out.println("线程"+name+"打印"+num);
condition.signalAll();//唤醒阻塞队列的所有等待线程
//打印后进入阻塞状态
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}finally {
System.out.println("你到底执没执行?");
lock.unlock();//解锁
}
}
结果
搞定了。但只是实现了这样的功能,那之前的问题为什么会产生呢?
-
为什么开始2个线程都堵塞了?
根据代码的逻辑顺序可以推论,最开始先创建了线程1,然后线程1执行打印任务,然后线程1就阻塞了,没有执行唤醒语句和解锁语句;
之后创建了线程2,也执行打印语句,后阻塞,也没有执行唤醒语句,所以最后两个线程都阻塞了,程序也就阻塞了 -
如果finally没执行,线程2怎么拿到锁的?
如果不将唤醒语句放在阻塞语句之前,可知finally语句根本不会执行,解锁语句也不会执行。说明只要该线程进入了阻塞状态,后面的语句就会一直等待,直到被唤醒,所以“你到底执行没有”会在第3句的位置开始打印,后面每间隔一个语句就会打印一次。
那如果开始finally语句没执行,锁并没有被解开,线程2如何拿到锁的呢?
推理一下
只有一种可能
这种可能
就是
执行await方法
就会
自动
解锁!!!
1.7 感悟
感觉编程语言真的很‘冷冰冰’,完全是逻辑的实现,但又会感觉这种只是逻辑、没有感情的东西有一种魅力,一种自己暂时无法言语的美,对于这种美,我无法抗拒。
我会想,这种美为什么会产生呢?为什么我会觉得美?