Java中juc并发包下的Condition接口与ReentrantLock对象锁实现线程通信
前言
在传统的Java开发中,大多数程序员都是使用synchronized关键字配合Object类中的wait()、notify()方法和notifyAll()方法来实现线程通信,不过随着jdk版本的不断升级与维护,在jdk1.5开始,JavaAPI中出现了一个叫ReentrantLock对象锁,它是一种可重入的、递归的非公平锁,下面我们就来简单介绍一下ReentrantLock锁。
ReentrantLock继承结构体系图
synchronized关键字与ReentrantLock重入锁的对比
Synchronized是依赖于JVM实现的,而ReenTrantLock是JDK实现的。与synchronized相比,重入锁有着显式的操作过程,开发人员必须手动的指定何时加锁(调用lock方法),何时释放锁(调用unlock方法)。因此,重入锁对逻辑的控制性要好于synchronized。重入锁,顾名思义,对于同一个线程,这种锁是可以反复进入的。如果一个线程多次获得锁,那么在释放锁的时候,也必须释放相同的次数。
构造器
常用方法
1 lock() 2 3 获得锁,如果锁已经被占用,则等待 4 5 lockInterruptibly() 6 7 获得锁,但优先响应中断。如果当前线程未被中断,则获取锁 8 9 tryLock() 10 11 尝试获得锁,如果成功放回true,失败返回false,该方法不等待,立即返回 12 13 unlock() 14 15 释放锁 16 17 newCondition() 18 19 返回与lock实例一起使用的Condition对象
案例演示
需求:
现有三个线程A、B、C,要求这三个线程以A -> B -> C 这种顺序来轮流干活(10个轮回)
具体实现:
通过实现Condition对象来实现多线程之间通信
通过一个变量num来标识当前该哪个线程干活
规定:
num = 1时,线程A干活
num = 2时,线程B干活
num = 3时,线程C干活
测试代码:
1 package com.lzp.lock.condition; 2 3 import java.util.concurrent.locks.Condition; 4 import java.util.concurrent.locks.Lock; 5 import java.util.concurrent.locks.ReentrantLock; 6 7 /** 8 * @Author LZP 9 * @Date 2021/7/1 20:41 10 * @Version 1.0 11 * 12 * 需求: 13 * 现有三个线程A、B、C,要求这三个线程以A -> B -> C 这种顺序来轮流干活(10个轮回) 14 * 通过实现Condition对象来实现多线程之间的通信 15 * 通过一个变量num来标识当前该哪个线程干活 16 * 规定: 17 * num = 1时,线程A干活 18 * num = 2时,线程B干活 19 * num = 3时,线程C干活 20 */ 21 public class ConditionDemo01 { 22 23 private int num = 1; 24 25 private final Lock lock = new ReentrantLock(); 26 private final Condition cA = lock.newCondition(); 27 private final Condition cB = lock.newCondition(); 28 private final Condition cC = lock.newCondition(); 29 30 public void doA(int i) { 31 // 上锁 32 lock.lock(); 33 try { 34 while (num != 1) { 35 // 等待 36 try { 37 cA.await(); 38 } catch (InterruptedException e) { 39 e.printStackTrace(); 40 } 41 } 42 // 干活 43 System.out.println(Thread.currentThread().getName() + "\t" + i); 44 // 修改标志位 45 num = 2; 46 // 通知线程B 47 cB.signal(); 48 } finally { 49 lock.unlock(); 50 } 51 } 52 53 public void doB(int i) { 54 // 上锁 55 lock.lock(); 56 try { 57 while (num != 2) { 58 // 等待 59 try { 60 cB.await(); 61 } catch (InterruptedException e) { 62 e.printStackTrace(); 63 } 64 } 65 // 干活 66 System.out.println(Thread.currentThread().getName() + "\t" + i); 67 // 修改标志位 68 num = 3; 69 // 通知线程C 70 cC.signal(); 71 } finally { 72 lock.unlock(); 73 } 74 } 75 76 public void doC(int i) { 77 // 上锁 78 lock.lock(); 79 try { 80 while (num != 3) { 81 // 等待 82 try { 83 cC.await(); 84 } catch (InterruptedException e) { 85 e.printStackTrace(); 86 } 87 } 88 // 干活 89 System.out.println(Thread.currentThread().getName() + "\t" + i); 90 // 修改标志位 91 num = 1; 92 // 通知线程A 93 cA.signal(); 94 } finally { 95 lock.unlock(); 96 } 97 } 98 99 public static void main(String[] args) { 100 ConditionDemo01 source = new ConditionDemo01(); 101 new Thread(() -> { 102 for (int i = 0; i < 10; i++) { 103 source.doA(i); 104 } 105 }, "A").start(); 106 new Thread(() -> { 107 for (int i = 0; i < 10; i++) { 108 source.doB(i); 109 } 110 }, "B").start(); 111 new Thread(() -> { 112 for (int i = 0; i < 10; i++) { 113 source.doC(i); 114 } 115 }, "C").start(); 116 } 117 }
运行结果:
ReentrantLock的特点
1. ReentrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。
2. ReentrantLock提供了一个Condition(条件)类,用来实现分组唤醒需要唤醒的线程们,而不是像synchronized要么随机唤醒一个线程要么唤醒全部线程。
3. ReentrantLock提供了一种能够中断等待锁的线程的机制,通过lock.lockInterruptibly()来实现这个机制。应用场景:需要使用以上三个特点时,使用ReentrantLock。
应用场景:需要使用以上三个特点时,使用ReentrantLock。
Condition的使用
1. 使用ReentrantLock类的newCondition()方法可以获取Condition对象
2. 需要等待的时候使用Condition的await()方法, 唤醒的时候用signal()方法
3. 不同的线程使用不同的Condition, 这样就能区分唤醒的时候找哪个线程了