ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
[toc] # 1. Condition类 ReentrantLock 与 Condition 组合实现线程同步机制,防止线程访问共享资源发生冲突的一个重要原因就是:<mark>当线程已经进入临界区(共享资源区)时才发现,居然还需要满足一定的条件才能够运行</mark>,那么这种情况该如何解决资源分配问题。 <br/> **1. 组合如下** ``` private Lock myLock = new ReentrantLock(); private Conditoin myCondition = myLock.newCondition(); ... myLock.lock(); // 加锁 ... try { while(运行条件) { myCondition.await(); // 如果条件不满足则阻塞线程并解锁 } ... myCondition.signalAll(); // 解除线程阻塞,通知线程去检测条件是否满足 } finally { myLock.unlock(); // 解锁 } ``` 资源分配规则为:假设有线程A、B、C。 <br/> 当线程A进入临界区发现不满足运行条件,则调用`await`方法使线程A进入等待集中,并释放锁。 <br/> 当线程B进入临界区发现也不满足运行条件,则调用`await`方法使线程B进入等待集中,并释放锁。 <br/> 当线程C进入临界区发现满足运行条件,则不调用`await`方法,而去调用`signalAll`方法,解除线程A和线程B的阻塞状态,等待线程C从临界区出去以后,线程A和线程B通过竞争获取对临界区的访问机会。 <br/> **2. 意外情况** 假设线程A调用`await`方法后,线程A是无法自动唤醒的,只能是被其他线程调用`signalAll`方法唤醒,否则线程A永远无法被唤醒。 <br/> 该组合可能还会出现一种情况:当最后一个线程在唤醒其他线程前自身也进入了等待集,则整个程序就会挂起了,导致了死锁现象。就是泥菩萨过河,自身难保,谁也救不了了。解决的经验就是:应该在有利于解除其他被阻塞状态的地方调用`signalAll`方法。 <br/> **3. 案例演示** 下面的程序实现100个线程共同打印一个变量`sum`,打印的条件是`sum`必须是2的倍数,否则进入等待状态。该程序最终还是会出现阻塞。 ``` public class LockAndConditionTest { int sum = 0; private Lock myLock = new ReentrantLock(); // 创建锁对象 private Condition myCondition = myLock.newCondition(); // 创建条件对象 public LockAndConditionTest() { for (int i = 1; i <= 100; i++) { // 使用for创建100个线程 Runnable r = () -> { try { while (true) { myLock.lock(); // 加锁 try { ++sum; while (sum % 2 != 0) { System.out.println("{线程名: " + Thread.currentThread().getName() + ", 状态: 进入等待集, 原因: " + sum + "不是2的倍数}"); myCondition.await(); // 如果不满足运行条件则进入等待状态并释放锁 } System.out.println("{线程名: " + Thread.currentThread().getName() + ", 状态: 通过, 原因: " + sum + "是2的倍数}"); Thread.sleep(100); myCondition.signalAll(); // 解除所有被阻塞线程的阻塞状态 } finally { myLock.unlock(); // 解锁 } } } catch (InterruptedException e) { } }; Thread t = new Thread(r); t.setName("线程" + i); t.start(); // 启动线程 } } public static void main(String[] args) { new LockAndConditionTest(); } } ``` 某一次运行的结果如下: ```java {线程名: 线程97, 状态: 通过, 原因: 192是2的倍数} {线程名: 线程97, 状态: 进入等待集, 原因: 193不是2的倍数} {线程名: 线程98, 状态: 通过, 原因: 194是2的倍数} {线程名: 线程98, 状态: 进入等待集, 原因: 195不是2的倍数} {线程名: 线程99, 状态: 通过, 原因: 196是2的倍数} {线程名: 线程99, 状态: 进入等待集, 原因: 197不是2的倍数} {线程名: 线程100, 状态: 通过, 原因: 198是2的倍数} {线程名: 线程100, 状态: 进入等待集, 原因: 199不是2的倍数} {线程名: 线程1, 状态: 进入等待集, 原因: 199不是2的倍数} {线程名: 线程4, 状态: 进入等待集, 原因: 199不是2的倍数} {线程名: 线程2, 状态: 进入等待集, 原因: 199不是2的倍数} {线程名: 线程6, 状态: 进入等待集, 原因: 199不是2的倍数} ----跑到 199 程序就被阻塞了 ----- ``` <br/> # 2. 锁测试与超时 线程调用`lock`方法获取其它线程的锁的时候可能会发生阻塞,为了避免这种情况可以调用Lock类的`tryLock`方法试图申请一个锁,如果申请到锁着返回true,否则返回false,并离开去做其它事。 ``` private Lock myLock = new ReentrantLock(); private Condition myCondition = myLock.getCondition(); ... if (myLock.tryLock()) { // 或者myLock.tryLock(100, TimeUnit.MILLISECONDS) ... try { ... } finally { myLock.unlock(); } } else { } ``` * java.util.concurrent.locks.Lock 5.0 * Condition newCondition() 返回一个与锁相关条件对象Condition * java.util.concurrent.locks.Condition 5.0 * void await() 将线程放入到条件等待集中。 * void signalAll(); 解除所有在条件等待集中的线程的阻塞状态。 * void signal() 从条件等待集中随机解除一个线程的阻塞状态。相比于`signalAll`方法解除阻塞线程的速度更快,但危险大,因为如果被随机选择的线程依然不能够被解除阻塞,而再无其他线程调用`signal`方法了,那么系统就会死锁了,这就是绝望啊!。