并发(五、锁 Lock)

1、Lock

java.util.concurrent 包简称 JUC,Lock 就在这个包下的 locks 包下。包含如下方法:

  • void lock():获取锁
  • void lockInterruptibly():可中断的获取锁
  • boolean tryLock():尝试非阻塞的获取锁,立即返回boolean
  • boolean tryLock(long time, TimeUnit unit):功能同上,超时的获取锁
  • void unlock():释放锁
  • Condition newCondition():这是一个获取等待通知的组件

2、CAS

CAS(Compare And Swap)比较并交换 是一种原子操作,用于实现多线程环境下的同步操作。 CAS 操作包含三个操作数:内存位置(V)、旧的预期值(A)和新值(B)。当且仅当预期值 A 和内存位置 V 的值相同时,CAS 会将内存位置 V 的值更新为新值 B。

主要包含以下三个步骤:

  1. 读取:读取内存位置V的值。
  2. 比较:将读取到的值与期望的原值A进行比较。
  3. 交换:如果读取到的值等于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 的经典三大问题

  1. CAS 采用自旋的方式,循环性能开销大,浪费CPU资源
    1. 优化方案:在Java中,很多使用自旋CAS的地方,会有一个自旋次数的限制,超过一定次数,就停止自旋。
  2. 不能保证代码块的原子性,只能保证一个变量的原子性操作
    1. 优化方案1:可以考虑改用 juc 包中的锁来保证操作的原子性。
    2. 优化方案2:使用原子类,或可以考虑合并多个变量,将多个变量封装成一个对象,通过 AtomicReference 来保证原子性。
    3. 优化方案3:使用 asynchronized
  3. 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能干什么?

  1. 同步队列的管理和维护
  2. 同步状态管理
  3. 线程阻塞、唤醒管理

独占锁:也叫互斥锁、排它锁,就是一个线程获得了锁,那么其他想要获取这把锁的线程就得等待。

共享锁:可以多个线程同时获取的锁,典型的如:读/写锁里的读锁。

  • 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 有以下优势:

  1. park/unpark 是以 thread 为操作对象,语义更直观
  2. park/unpark 操作更精准、更灵活,可以准确的唤醒指定线程
  3. 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

  1. 公平锁是指多个线程按照申请锁的顺序来获取锁,线程直接进入队列中排队,队 列中的第一个线程才能获得锁。
  2. 公平锁的优点是等待锁的线程不会饿死。缺点是整体吞吐效率相对非公平锁要低,等待队列中除第一个线程以外的所有线程都会阻塞,CPU 唤醒阻塞线程的开 销比非公平锁大。

非公平锁 NonfairSync

  1. 非公平锁是多个线程加锁时直接尝试获取锁,获取不到才会到等待队列的队尾等待。但如果此时锁刚好可用,那么这个线程可以无需阻塞直接获取到锁。
  2. 非公平锁的优点是可以减少唤起线程的开销,整体的吞吐效率高,因为线程有几率不阻塞直接获得锁,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 不用手动释放锁(即便发生异常也会自动释放锁)。
区别synchronizedReentrantLock
锁实现机制对象头监视器模式依赖AQS
灵活性不灵活支持响应中断、超时、尝试获取锁
释放锁形式自动显式调用 unlock()
支持锁类型非公平锁公平锁 & 非公平锁
条件队列单条件队列多条件队列
可重入支持支持支持

7、ReentrantReadWriteLock

1、读锁 和 写锁

读锁:用在读取数据(临界资源)的地方。

写锁:用在更新数据(临界资源)的地方。

读锁、写锁 的互斥规则:(以下为不同线程的操作)

  1. 读 - 读:共享,意味着他们都可以拿到锁(共享锁)
  2. 读 - 写:互斥
  3. 写 - 读:互斥
  4. 写 - 写:互斥

2、简介

ReentrantReadWriteLock 实现了 ReadWriteLock 接口,其内容如下:

public interface ReadWriteLock {
    Lock readLock();
    Lock writeLock();
}

ReentrantReadWriteLock 有以下功能:

  1. 支持读锁、写锁
  2. 支持公平锁、非公平锁
  3. 支持可重入锁
  4. 支持锁降级(写锁的降级:写锁降级成为读锁)
    1. 如果一个线程持有写锁,在不释放写锁的情况下,它还可以继续持有读锁,这种情况就是写锁降级
    2. 遵循规则:先获取写锁,再获取读锁,然后释放写锁 的次序
    3. 如果释放了写锁,就完全转换为读锁

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 锁会遇到的问题:

  1. 写线程 “饥饿” 问题
  2. 如果有线程在读,那么写线程无法获取锁(悲观锁策略)

▼ 基于上面的问题,Java8 引入 StampedLock:

对 ReentrantReadWriteLock 进行了增强,优化了读锁、写锁的访问和相互转换,因此可以更细力度的控制并发。

▼ 但是 StampedLock 也存在明显的问题:

  1. 设计初衷是作为一个内部工具类使用,用于辅助开发其他的线程安全组件(用不好的话容易产生死锁,甚至莫名其妙的问题)
  2. 这个锁不支持重入,所以在使用时非常受限(很鸡肋,了解会用即可)

▼ StampedLock 特点:

  1. 所有获取锁的方法都会返回一个 stamp
  2. 所有释放锁的方法都需要传一个 stamp
  3. 是不可重入的
  4. 有三种访问方式
    1. reading:读模式
    2. writing:写模式
    3. optimistic reading:乐观读模式
  5. 支持读锁和写锁的相互转化
  6. 不支持 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();
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

纯纯的小白

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值