本文共 4469 字,大约阅读时间需要 14 分钟。
最近使用Condition时有一些疑惑,于是自己做了几个实验,了解了Condition的具体用法,现记录如下。
首先我们直接来看一段之前网上看到的一个输出1-9的例子,当然网上那个例子是有缺陷的,并不能保证每次都能输出1-9,后面会介绍原因,先看如下代码:
public class ConditionTest { public static void main(String[] args) { final AtomicInteger value = new AtomicInteger(1); final Lock lock = new ReentrantLock(); final Condition reachThreeCondition = lock.newCondition(); final Condition reachSixCondition = lock.newCondition(); Thread threadA = new Thread(new Runnable() { public void run() { lock.lock(); System.out.println("线程1拿到锁【1】"); try { // 输出1-3 之后激活reachThreeCondition while (value.get() <= 3) { System.out.println(value.get()); value.addAndGet(1); } reachThreeCondition.signal(); System.out.println("线程1【1】调用唤醒【reachThreeCondition】"); } finally { lock.unlock(); System.out.println("线程1【1】释放锁"); } lock.lock(); System.out.println("线程1拿到锁【2】"); try { // 挂起等待唤醒reachSixCondition操作则继续输出后续数字 System.out.println("线程1【2】进入等待"); reachSixCondition.await(); System.out.println("线程1【2】被唤醒"); // 输出剩余数字 while (value.get() <= 9) { System.out.println(value.get()); value.addAndGet(1); } } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } }); Thread threadB = new Thread(new Runnable() { public void run() { try { lock.lock(); System.out.println("线程2拿到锁【1】"); while (value.get() <= 3) { // 线程挂起等待输出1-3之后唤醒 System.out.println("线程2【1】进入等待"); reachThreeCondition.await(); System.out.println("线程2【1】被唤醒"); } } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); System.out.println("线程2【1】释放了锁"); } try { lock.lock(); System.out.println("线程2拿到锁【2】"); while (value.get() <= 6) { System.out.println(value.get()); value.addAndGet(1); } // 唤醒reachSixCondition继续输出后续数字 reachSixCondition.signal(); System.out.println("线程2【2】唤醒了【reachSixCondition】"); } finally { lock.unlock(); System.out.println("线程2【2】释放了锁"); } } }); // 启动两个线程 threadB.start(); threadA.start(); }}
输出结果1:
线程2拿到锁【1】线程2【1】进入等待线程1拿到锁【1】123线程1【1】调用唤醒【reachThreeCondition】线程1【1】释放锁线程1拿到锁【2】线程1【2】进入等待线程2【1】被唤醒线程2【1】释放了锁线程2拿到锁【2】456线程2【2】唤醒了【reachSixCondition】线程2【2】释放了锁线程1【2】被唤醒789
输出结果2:
线程2拿到锁【1】线程2【1】进入等待线程1拿到锁【1】123线程1【1】调用唤醒【reachThreeCondition】线程1【1】释放锁线程2【1】被唤醒线程2【1】释放了锁线程2拿到锁【2】456线程2【2】唤醒了【reachSixCondition】线程2【2】释放了锁线程1拿到锁【2】线程1【2】进入等待
输出结果3:
线程1拿到锁【1】123线程1【1】调用唤醒【reachThreeCondition】线程1【1】释放锁线程1拿到锁【2】线程1【2】进入等待线程2拿到锁【1】线程2【1】释放了锁线程2拿到锁【2】456线程2【2】唤醒了【reachSixCondition】线程2【2】释放了锁线程1【2】被唤醒789
上面列出了三种输出结果,结果1其实是一个很理想的运行结果,所有代码都执行完了,其实读者可以反复运行上面代码,会发现可能出现好几种结果,这里只列出三种典型的结果。
我们来根据结果做如下分析: 1. 线程1和线程2同时启动,此时首次出现锁竞争,上述结果1是线程2获取到锁(此处也可能是线程1获取到锁,见结果3),则线程1处于挂起状态,等待获取锁,线程2获取锁后判断满足条件,则使用reachThreeCondition.await()让线程进入挂起状态,且此处会释放锁。 2. 由于上一步线程2释放了锁,所以此时线程1可获取到锁,于是线程1获取到锁,并继续执行输出123,之后调用reachThreeCondition.signal()唤醒线程2,此时线程1和线程2同时运行,线程1和2分别继续执行后续释放锁的代码(此时线程2其实没有锁可释放)。 3. 此时又会出现锁竞争,线程1和线程2会竞争锁(结果1和结果3是由线程1拿到了锁,结果2是由线程2拿到了锁),此时分两种情况: a.(1)线程1拿到锁,之后满足条件通过reachSixCondition挂起并释放锁,(2)此时线程2拿到锁输出456之后唤醒reachSixCondition,之后俩线程继续各自执行后续代码到程序结束,完整输出1-9. b.线程2拿到锁,之后不满足条件释放锁,此时第三次出现锁竞争,会出现两种结果,1)线程1拿到锁则执行a逻辑,完整输出1-9;2)线程2拿到锁,则执行线程2输出4-6,之后释放锁,线程1获取锁,之后执行reachSixCondition挂起,由于此时线程2已执行结束,则线程1的reachSixCondition没有地方被唤醒,所以线程1会一直处于挂起状态。所以通过上面跟踪代码,理解了Condition的使用,其实在于可以根据不同条件来挂起、唤醒线程,且会自动释放锁,以达到可以更加方便的实现线程的有序性。
欢迎关注个人博客:blog.scarlettbai.com
转载地址:http://lesni.baihongyu.com/