文章目录
1、Lock
java.util.concurrent 包简称 JUC,Lock 就在这个包下的 locks 包下。包含如下方法:
void lock():获取锁void lockInterruptibly():可中断的获取锁boolean tryLock():尝试非阻塞的获取锁,立即返回booleanboolean tryLock(long time, TimeUnit unit):功能同上,超时的获取锁void unlock():释放锁Condition newCondition():这是一个获取等待通知的组件
2、CAS
CAS(Compare And Swap)比较并交换 是一种原子操作,用于实现多线程环境下的同步操作。 CAS 操作包含三个操作数:内存位置(V)、旧的预期值(A)和新值(B)。当且仅当预期值 A 和内存位置 V 的值相同时,CAS 会将内存位置 V 的值更新为新值 B。
主要包含以下三个步骤:
- 读取:读取内存位置V的值。
- 比较:将读取到的值与期望的原值A进行比较。
- 交换:如果读取到的值等于A,则将内存位置V的值设置为新值B;否则开始 自旋(Spinning)来持续尝试重新执行CAS操作,直到成功为止。
无锁的概念:
- 乐观派:无锁,总是认为对临界资源的访问没有冲突。一旦发生冲突则采用 CAS 技术来保证线程安全。这就是乐观锁。
- 悲观派:加锁,总是认为对临界资源的访问存在冲突。synchronized 就是悲观锁。
◎ CAS 实现的基石:Unsafe 类
CAS 想要保证线程是安全的,一个实现的关键就是要保证 比较并交换 是一个原子操作。
在 Java 里面就是用 Unsafe 类来实现 CAS 的原子操作,它通过 JNI(Java Native Interface;Java本地接口) 方式去访问本地的 C++库,从而使得 Java 拥有能够直接操作内存空间的能力。
◎ CAS 在 Java 中的应用
比如:java.util.concurrent.atomic 包、java.util.concurrent.locks 包下面的一系列的类。
在 JDK1.6+,synchronized 升级为重量级锁之前,也是采用的 CAS 机制。
◎ CAS 的经典三大问题
- CAS 采用自旋的方式,循环性能开销大,浪费CPU资源
- 优化方案:在Java中,很多使用自旋CAS的地方,会有一个自旋次数的限制,超过一定次数,就停止自旋。
- 不能保证代码块的原子性,只能保证一个变量的原子性操作
- 优化方案1:可以考虑改用 juc 包中的锁来保证操作的原子性。
- 优化方案2:使用原子类,或可以考虑合并多个变量,将多个变量封装成一个对象,通过 AtomicReference 来保证原子性。
- 优化方案3:使用 asynchronized
- ABA问题:并发环境下,假设初始条件是A,去修改数据时,发现是A就会执行修改。但是看到的虽然是A,中间可能发生了A变B,B又变回A的情况。此时A已经非彼A,数据即使成功修改,也可能有问题。
怎么解决 ABA问题?加版本号。
每次修改变量,都在这个变量的版本号上加1,这样,刚刚A->B->A,虽然A的值没变,但是它的版本号已经变了,再判断版本号就会发现此时的A已经被改过了。参考乐观锁的版本号,这种做法可以给数据带上了一种实效性的检验。
Java提供了 AtomicStampReference 类,它的 compareAndSet 方法首先检查当前的对象引用值是否等于预期引用,并且当前印戳(Stamp)标志是否等于预期标志,如果全部相等,则以原子方式将引用值和印戳标志的值更新为给定的更新值。
3、AQS
AbstractQueuedSynchronizer 抽象同步队列,简称 AQS ,它是 Java并发包的根基,并发包中的锁就是基于AQS实现的。
AQS能干什么?
- 同步队列的管理和维护
- 同步状态管理
- 线程阻塞、唤醒管理
独占锁:也叫互斥锁、排它锁,就是一个线程获得了锁,那么其他想要获取这把锁的线程就得等待。
共享锁:可以多个线程同时获取的锁,典型的如:读/写锁里的读锁。
- AQS是一个一个 FIFO 的双向队列(基于 CLH 队列实现),Node 节点中的 thread 变量用来存放进入 AQS队列里的线程,SHARED 表示是获取共享资源时被阻塞挂起后放入 AQS队列的, EXCLUSIVE 表示是获取独占资源时被挂起后放入 AQS队列的。
- AQS 使用一个 volatile 修饰的 int 类型的成员变量 state 来表示同步状态,修改同步状态成功即为获得锁,volatile 保证了变量在多线程之间的可见性,修改 state 值时通过 CAS 机制来保证修改的原子性。
- 获取 state 的方式分为两种,独占方式
tryAcquire()和共享方式tryAcquireShared(), 一个线程使用独占方式获取了资源,其它线程就会在获取失败后被阻塞。一个线程使用共享方式获取了资源,另外一个线程还可以通过 CAS 的方式进行获取,tryAcquire()和tryAcquireShared()是抽象方法,需要子类自行实现。 - 如果共享资源被占用,需要一定的阻塞等待唤醒机制来保证锁的分配,AQS 中会将竞争共享资源失败的线程添加到队列中,线程进入队列后会进行自旋,自旋一定次数后,会使用
LockSupport.park()进入阻塞状态。 - 获取到锁的线程可以重入,每重入一次,state+1,释放资源的时候,会使用 CAS 操作将 state 修改为 0,重入多少次,释放多少次,并使用
LockSupport.unpark()唤醒处于等待状态的线程。
CLH 是什么:AQS 是 CLH(Craig、Landin、Hagersten)锁定队列的变体。CLH队列结构如下:
● 用法
使用这个类用作同步的基础上,重新定义以下方法,如适用,通过检查和/或修改使用所述同步状态 getState() , setState(int) 和/或 compareAndSetState(int, int) :
● 独占锁和共享锁实现差异
▼ 出队差异
正常情况下,独占锁,只有持有锁的线程运行结束了,释放锁后独占锁的节点才会出队。
共享锁,是在唤醒了下一个节点,并且被唤醒的节点成功被设置为 head 节点后,自己这个节点就出队了。
▼ 唤醒差异
独占锁,只有在释放锁的时候,才会去看看要不要唤醒下一个节点。
共享锁,是在两个地方去看看是不是要唤醒下一个节点;一个是在获取锁的过程中调用 setHeadAndPropagate 方法的时候,另一个是释放锁的过程中调用 unparkSuccessor 方法的时候.
4、Demo 锁
4.1、synchronized 实现锁(Low)
1、简单锁
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
public class MyLock implements Lock {
private boolean hasLocked = false;
@Override
public synchronized void lock() {
while(hasLocked){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(!hasLocked)
hasLocked = true;
}
@Override
public synchronized void unlock() {
if(hasLocked) {
hasLocked = false;
notifyAll();
}
}
// 其余 Override 略(主要是没写)
}
2、可重入锁
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
public class MyReentrantLock implements Lock {
private boolean hasLocked = false;
private Thread current = null;
private int count = 0;
@Override
public synchronized void lock() {
Thread entryThread = Thread.currentThread();
while(hasLocked && !entryThread.equals(current)){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(!hasLocked) {
hasLocked = true;
current = entryThread;
count++;
} else if (entryThread.equals(current)){
count++;
}
}
@Override
public synchronized void unlock() {
if(hasLocked) {
if(Thread.currentThread().equals(current)){
count--;
if(count == 0){
hasLocked = false;
current = null;
notifyAll();
}
}
}
}
// 其余 Override 略(主要是没写)
}
4.2、AQS 实现锁
1、简单锁
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
/**
* 不可重入锁
*/
public class MyLock implements Lock {
private final Sync sync = new Sync();
private class Sync extends AbstractQueuedSynchronizer {
// 尝试以独占模式获取锁
public boolean tryAcquire(int acquires){
assert acquires == 1;
if(compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
// 尝试释放锁(该方法总是由执行释放的线程调用)
protected boolean tryRelease(int releases) {
assert releases == 1;
if(!isHeldExclusively()){
throw new IllegalMonitorStateException();
}
setExclusiveOwnerThread(null);
setState(0);
return true;
}
public boolean isLocked() {
return getState() != 0;
}
// 返回true如果同步仅针对当前(调用)线程进行保存
public boolean isHeldExclusively() {
return getExclusiveOwnerThread() == Thread.currentThread();
}
public Condition newCondition() {
return new ConditionObject();
}
}
@Override
public void lock() {
sync.acquire(1);
}
@Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
@Override
public boolean tryLock() {
return sync.tryAcquire(1);
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(time));
}
@Override
public void unlock() {
sync.release(1);
}
@Override
public Condition newCondition() {
return sync.newCondition();
}
}
2、可重入锁
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
/**
* 可重入锁
*/
public class MyReentrantLock implements Lock {
private final Sync sync = new Sync();
private class Sync extends AbstractQueuedSynchronizer {
// 尝试以独占模式获取锁
public boolean tryAcquire(int acquires){
assert acquires == 1;
if(compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
} else if(Thread.currentThread().equals(getExclusiveOwnerThread())){
setState(getState() + 1);
return true;
}
return false;
}
// 尝试释放锁(该方法总是由执行释放的线程调用)
protected boolean tryRelease(int releases) {
assert releases == 1;
if(!isHeldExclusively()){
throw new IllegalMonitorStateException();
}
setState(getState() - 1);
if(getState() == 0){
setExclusiveOwnerThread(null);
return true;
}
return false;
}
public boolean isLocked() {
return getState() != 0;
}
// 返回true如果同步仅针对当前(调用)线程进行保存
public boolean isHeldExclusively() {
return getExclusiveOwnerThread() == Thread.currentThread();
}
public Condition newCondition() {
return new ConditionObject();
}
}
@Override
public void lock() {
sync.acquire(1);
}
@Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
@Override
public boolean tryLock() {
return sync.tryAcquire(1);
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(time));
}
@Override
public void unlock() {
sync.release(1);
}
@Override
public Condition newCondition() {
return sync.newCondition();
}
}
5、工具类 LockSupport
LockSupport 是一个变成工具类,主要为了 阻塞线程(park)和唤醒线程(unpark)的时候使用。
设计核心:许可。
- park:挂起当前线程,等待一个许可
- unpark:为指定线程提供许可,唤醒指定的线程
和 wait/notify 类似,park/unpark 有以下优势:
- park/unpark 是以 thread 为操作对象,语义更直观
- park/unpark 操作更精准、更灵活,可以准确的唤醒指定线程
- wait/notify 是和 synchoronized 联系在一起的,wait 后现成进入 Blocked 状态;park 方法使当前线程挂起,进入 Waiting 状态
demo
@Test
public class LockSupportTest {
@SneakyThrows
public void test() {
MyLockSupportTest1 t = new MyLockSupportTest1();
t.start();
// 也可以在这里调 LockSupport.unpark(t); 直接先给许可,当然如果写多个也只会给1个许可,不会叠加
// 如果提前获得许可,那在第一次调用挂起的时候 就会直接消耗这个许可继续运行
System.out.println("activeCount 111 === " + Thread.activeCount());
showThreads();
Thread.sleep(3000l);
System.out.println("bocker === " + LockSupport.getBlocker(t));
System.out.println("activeCount 222 === " + Thread.activeCount());
showThreads();
System.out.println("给予许可+++");
LockSupport.unpark(t);
System.out.println("activeCount 333 === " + Thread.activeCount());
showThreads();
}
public void showThreads(){
Thread[] tarr = new Thread[Thread.activeCount()];
Thread.currentThread().enumerate(tarr);
System.out.println("==============================");
Arrays.stream(tarr).forEach(t -> {
System.out.println("当前活跃线程:" + t.getId() + "--" + t.getName());
});
System.out.println("==============================");
}
}
class MyLockSupportTest1 extends Thread {
private int num = 0;
@SneakyThrows
@Override
public void run() {
while(true){
num++;
System.out.println("num is : " + num);
Thread.sleep(10L);
// 根据条件挂起当前线程
if(num >= 20){
System.out.println("挂起时 num = " + num);
LockSupport.park();
// LockSupport.park(new XXX());
// LockSupport.parkNanos(new XXX(), 3 * 1000 * 1000000);
num = 5;
}
}
}
}
6、ReentrantLock
ReentrantLock通过Sync类,间接继承了AQS。

它是可重入的独占锁,只能有一个线程可以获取该锁,其它获取该锁的线程会被阻塞而被放入该锁的阻塞队列里面。
ReentrantLock 的加锁操作:
// 创建非公平锁
ReentrantLock lock = new ReentrantLock();
// 获取锁操作
lock.lock();
try {
// 执行代码逻辑
} catch (Exception ex) {
// ...
} finally {
// 解锁操作
lock.unlock();
}
new ReentrantLock() 构造函数默认创建的是非公平锁 NonfairSync。如果传参 true 就会创建公平锁 FairSync。
NonfairSync、FairSync 两者都是 ReentrantLock 静态内部类,只不过实现不同锁语义。
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
公平锁的实现上在获取锁 tryAcquire 的时候多了一个前置判断条件 !hasQueuedPredecessors(),如果有等待队列则直接返回 false 则扔等待队列 tail 排队去。
默认创建的对象 lock() 的时候:
- 如果锁当前没有被其它线程占用,并且当前线程之前没有获取过该锁,则当前线程会获取到该锁,然后设置当前锁的拥有者为当前线程,并设置 AQS 的状态值为1 ,然后直接返回。如果当前线程之前己经获取过该锁,则这次只是简单地把 AQS 的状态值加 1 后返回。
- 如果该锁己经被其他线程持有,非公平锁会尝试去获取锁,获取失败的话,则调用该方法线程会被放入 AQS 队列阻塞挂起。

● 公平锁 与 非公平锁
相对来说,非公平锁会有更好的性能,因为它的吞吐量比较大。当然,非公平锁让获取锁的时间变得更加不确定,可能会导致在阻塞队列中的线程长期处于饥饿状态。
公平锁 FairSync
- 公平锁是指多个线程按照申请锁的顺序来获取锁,线程直接进入队列中排队,队 列中的第一个线程才能获得锁。
- 公平锁的优点是等待锁的线程不会饿死。缺点是整体吞吐效率相对非公平锁要低,等待队列中除第一个线程以外的所有线程都会阻塞,CPU 唤醒阻塞线程的开 销比非公平锁大。
非公平锁 NonfairSync
- 非公平锁是多个线程加锁时直接尝试获取锁,获取不到才会到等待队列的队尾等待。但如果此时锁刚好可用,那么这个线程可以无需阻塞直接获取到锁。
- 非公平锁的优点是可以减少唤起线程的开销,整体的吞吐效率高,因为线程有几率不阻塞直接获得锁,CPU 不必唤醒所有线程。缺点是处于等待队列中的线程可能会饿死,或者等很久才会获得锁。
● synchronized 和 ReentrantLock 区别
- 锁的实现: synchronized 是 Java 语言的关键字,基于 JVM 实现。而 ReentrantLock 是基于 JDK 的 API 层面实现的(一般是
lock()和unlock()方法配合 try/finally 语句块来完成) - 性能: 在 JDK1.6 锁优化以前,synchronized 的性能比 ReenTrantLock 差很多。但是 JDK6 开始,增加了适应性自旋、锁消除等,两者性能就差不多了。
- 功能特点: ReentrantLock 比 synchronized 增加了一些高级功能,如等待可中断、可实现公平锁、可实现选择性通知。
- ReentrantLock 提供了一种能够中断等待锁的线程的机制,通过
lock.lockInterruptibly()来实现这个机制。 - ReentrantLock 可以指定是公平锁还是非公平锁。而 synchronized 只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。
- synchronized 与
wait()和notify()/notifyAll()方法结合实现等待/通知机制, ReentrantLock 类借助 Condition 接口与newCondition()方法实现。 - ReentrantLock 需要手工声明来加锁和释放锁,一般跟 finally 配合释放锁。而 synchronized 不用手动释放锁(即便发生异常也会自动释放锁)。
- ReentrantLock 提供了一种能够中断等待锁的线程的机制,通过
| 区别 | synchronized | ReentrantLock |
|---|---|---|
| 锁实现机制 | 对象头监视器模式 | 依赖AQS |
| 灵活性 | 不灵活 | 支持响应中断、超时、尝试获取锁 |
| 释放锁形式 | 自动 | 显式调用 unlock() |
| 支持锁类型 | 非公平锁 | 公平锁 & 非公平锁 |
| 条件队列 | 单条件队列 | 多条件队列 |
| 可重入支持 | 支持 | 支持 |
7、ReentrantReadWriteLock
1、读锁 和 写锁
读锁:用在读取数据(临界资源)的地方。
写锁:用在更新数据(临界资源)的地方。
读锁、写锁 的互斥规则:(以下为不同线程的操作)
- 读 - 读:共享,意味着他们都可以拿到锁(共享锁)
- 读 - 写:互斥
- 写 - 读:互斥
- 写 - 写:互斥
2、简介
ReentrantReadWriteLock 实现了 ReadWriteLock 接口,其内容如下:
public interface ReadWriteLock {
Lock readLock();
Lock writeLock();
}
ReentrantReadWriteLock 有以下功能:
- 支持读锁、写锁
- 支持公平锁、非公平锁
- 支持可重入锁
- 支持锁降级(写锁的降级:写锁降级成为读锁)
- 如果一个线程持有写锁,在不释放写锁的情况下,它还可以继续持有读锁,这种情况就是写锁降级
- 遵循规则:先获取写锁,再获取读锁,然后释放写锁 的次序
- 如果释放了写锁,就完全转换为读锁
ReentrantReadWriteLock 不支持锁升级。
3、简单使用
import lombok.SneakyThrows;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockTest {
private int num = 0;
// 其他线程修改的状态位
private volatile boolean flag = false;
// 初始化读写锁
private ReadWriteLock rw = new ReentrantReadWriteLock();
// 分别获取读锁和写锁
private Lock r = rw.readLock();
private Lock w = rw.writeLock();
@SneakyThrows
public int getNum(){
r.lock();
System.out.println("Read开始--" + Thread.currentThread().getName());
try {
Thread.sleep(3000L);
return num;
} finally {
System.out.println("Read结束--" + Thread.currentThread().getName());
r.unlock();
}
}
@SneakyThrows
public void setNum(){
w.lock();
System.out.println("Write开始--" + Thread.currentThread().getName());
try {
Thread.sleep(3000L);
num++;
} finally {
System.out.println("Write结束--" + Thread.currentThread().getName());
w.unlock();
}
}
@SneakyThrows
public void setNumJJ(){ // 测试写锁降级
w.lock();
System.out.println("Write开始--" + Thread.currentThread().getName());
try {
Thread.sleep(3000L);
num++; // 第一步写业务功能
r.lock();
System.out.println("写锁降级,获取读锁======" + Thread.currentThread().getName());
} finally {
System.out.println("Write结束--" + Thread.currentThread().getName());
w.unlock();
}
try {
if(flag) {
// 第二步读业务功能
System.out.println("执行读逻辑+++写锁降级为读锁--" + Thread.currentThread().getName());
}
} finally {
System.out.println("写锁降级,释放读锁======" + Thread.currentThread().getName());
r.unlock();
}
}
@SneakyThrows
public static void main(String[] args) {
ReadWriteLockTest t = new ReadWriteLockTest();
// 基础测试:读 - 读 / 读 - 写 / 写 - 写 / 写 - 读 测试
/*{
new Thread(()-> t.getNum() ).start();
new Thread(()-> t.getNum() ).start();
new Thread(()-> t.setNum() ).start();
new Thread(()-> t.setNum() ).start();
new Thread(()-> t.getNum() ).start();
new Thread(()-> t.getNum() ).start();
new Thread(()-> t.getNum() ).start();
}*/
// 测试写锁降级
{
new Thread(()-> t.getNum() ).start();
new Thread(()-> t.getNum() ).start();
new Thread(()-> t.setNumJJ() ).start();
new Thread(()-> t.flag = true).start();
new Thread(()-> t.getNum() ).start();
new Thread(()-> t.getNum() ).start();
new Thread(()-> t.getNum() ).start();
}
}
}
8、StampedLock
▼ ReentrantReadWriteLock 锁会遇到的问题:
- 写线程 “饥饿” 问题
- 如果有线程在读,那么写线程无法获取锁(悲观锁策略)
▼ 基于上面的问题,Java8 引入 StampedLock:
对 ReentrantReadWriteLock 进行了增强,优化了读锁、写锁的访问和相互转换,因此可以更细力度的控制并发。
▼ 但是 StampedLock 也存在明显的问题:
- 设计初衷是作为一个内部工具类使用,用于辅助开发其他的线程安全组件(用不好的话容易产生死锁,甚至莫名其妙的问题)
- 这个锁不支持重入,所以在使用时非常受限(很鸡肋,了解会用即可)
▼ StampedLock 特点:
- 所有获取锁的方法都会返回一个 stamp
- 所有释放锁的方法都需要传一个 stamp
- 是不可重入的
- 有三种访问方式
- reading:读模式
- writing:写模式
- optimistic reading:乐观读模式
- 支持读锁和写锁的相互转化
- 不支持 Condition
● 简单演示
import lombok.SneakyThrows;
import java.util.concurrent.locks.StampedLock;
public class StampedLockTest {
private int num = 0;
private StampedLock sl = new StampedLock();
@SneakyThrows
public int getNum(){
// 使用乐观锁
long stamp = sl.tryOptimisticRead();
// 先把数据取下来一次
int retNum = num;
System.out.println("获取乐观锁读取数据num===" + retNum + "---" + Thread.currentThread().getName());
Thread.sleep(2000l);
// 检查是否有写锁在操作,如果有写锁可能造成之前读取的数据已过时,因此要切换到普通读锁模式
if(!sl.validate(stamp)){
// 切换到普通读锁模式
stamp = sl.readLock();
try {
// 重新取数据
retNum = num;
System.out.println("乐观锁读取数据+发现有写锁,改用悲观读锁num===" + retNum + "---" + Thread.currentThread().getName());
Thread.sleep(3000l);
} finally {
System.out.println("释放悲观读锁----------");
sl.unlockRead(stamp);
}
}
System.out.println("返回读取的数据num===" + retNum + "---" + Thread.currentThread().getName());
return retNum;
}
@SneakyThrows
public void setNum(){
// 使用写锁,需要接收返回的stamp,解锁时用
long stamp = sl.writeLock();
System.out.println("Write锁获取===" + Thread.currentThread().getName());
try {
Thread.sleep(4000l);
num++;
} finally {
System.out.println("Write锁释放===" + Thread.currentThread().getName());
sl.unlockWrite(stamp);
}
}
public static void main(String[] args) {
StampedLockTest slt = new StampedLockTest();
new Thread(() -> slt.getNum()).start();
new Thread(() -> slt.setNum()).start();
new Thread(() -> slt.getNum()).start();
new Thread(() -> slt.getNum()).start();
}
}
结果:

9、Condition
用于对原生的 wait、notify、notifyAll 方法进行增强,从 Java 语言层面,实现类似的功能。源代码如下:
public interface Condition {
// 可中断wait
void await() throws InterruptedException;
// 不可中断wait
void awaitUninterruptibly();
// 超时wait1
long awaitNanos(long nanosTimeout) throws InterruptedException;
// 超时wait2
boolean await(long time, TimeUnit unit) throws InterruptedException;
// 超时wait3
boolean awaitUntil(Date deadline) throws InterruptedException;
// notify
void signal();
// notifyAll
void signalAll();
}
没错,它的实现就是 AQS 里的 ConditionObject。
● 使用
目标:实现控制多个方法的调用顺序。
◎ 原生实现
import lombok.SneakyThrows;
public class ConditionTest {
// 控制方法调用顺序的状态位
private int state = 1;
@SneakyThrows
public synchronized void t1(){
while(state != 1){
wait();
}
System.out.println("1111111");
state ++;
notifyAll();
}
@SneakyThrows
public synchronized void t2(){
while(state != 2){
wait();
}
System.out.println("2222222");
state ++;
notifyAll();
}
@SneakyThrows
public synchronized void t3(){
while(state != 3){
wait();
}
System.out.println("3333333");
state = 1;
notifyAll();
}
public static void main(String[] args) {
ConditionTest t = new ConditionTest();
new Thread(() -> {
for(;;)
t.t1();
}).start();
new Thread(() -> {
for(;;)
t.t2();
}).start();
new Thread(() -> {
for(;;)
t.t3();
}).start();
}
}
◎ Condition实现
import lombok.SneakyThrows;
public class ConditionTest {
// 控制方法调用顺序的状态位
private int state = 1;
private Lock lock = new ReentrantLock();
private Condition t1c = lock.newCondition();
private Condition t2c = lock.newCondition();
private Condition t3c = lock.newCondition();
@SneakyThrows
public void t1(){
lock.lock();
try{
while(state != 1){
t1c.await();
}
System.out.println("1111111");
state ++;
t2c.signal();
}finally {
lock.unlock();
}
}
@SneakyThrows
public void t2(){
lock.lock();
try{
while(state != 2){
t2c.await();
}
System.out.println("2222222");
state ++;
t3c.signal();
}finally {
lock.unlock();
}
}
@SneakyThrows
public void t3(){
lock.lock();
try{
while(state != 3){
t3c.await();
}
System.out.println("3333333");
state = 1;
t1c.signal();
}finally {
lock.unlock();
}
}
// 实现控制多个方法的调用顺序
public static void main(String[] args) {
ConditionTest t = new ConditionTest();
new Thread(() -> {
for(;;)
t.t1();
}).start();
new Thread(() -> {
for(;;)
t.t2();
}).start();
new Thread(() -> {
for(;;)
t.t3();
}).start();
}
}
◎ 生产者消费者改写
此处只修改了 公共区域,点击访问原版 生产者消费者模型 。
package pcsCondition;
import lombok.SneakyThrows;
import java.util.LinkedList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
// 柜台(公共区域)
public class Counter {
private LinkedList<Icecream> creamList = new LinkedList<>();
private final int MAX_COUNT = 10;
private Lock lock = new ReentrantLock();
private Condition producerCondition = lock.newCondition();
private Condition consumerCondition = lock.newCondition();
//生产者生产冰激凌放入柜台
@SneakyThrows
public void add(Icecream cream){
lock.lock();
try {
while (creamList.size() >= MAX_COUNT) {
System.out.println(Thread.currentThread().getName() +
":柜台满了,停止生产+++");
producerCondition.await();
}
creamList.add(cream);
System.out.println(Thread.currentThread().getName() +
":冰激凌已放入柜台,目前有【" + creamList.size() + "】个!!!");
consumerCondition.signalAll();
} finally {
lock.unlock();
}
}
// 消费者消费一个冰激凌
@SneakyThrows
public Icecream take(){
lock.lock();
try {
while(creamList.size() <= 0){
System.out.println(Thread.currentThread().getName() +
":柜台空了,等待生产---");
consumerCondition.await();
}
Icecream remove = creamList.remove();
System.out.println(Thread.currentThread().getName() +
":消费冰激凌1个,目前还剩【" + creamList.size() + "】个!!!");
producerCondition.signalAll();
return remove;
} finally {
lock.unlock();
}
}
}

170万+

被折叠的 条评论
为什么被折叠?



