AQS相关工实现类的使用及其原理

1、AQS

1.1、概述

全称是 AbstractQueuedSynchronizer,是阻塞式锁和相关的同步器工具的框架,其具有特点

  • state 属性来表示资源的状态(分独占模式和共享模式),子类需要定义如何维护这个状态,控制如何获取锁和释放锁
    • getState - 获取 state 状态
    • setState - 设置 state 状态
    • compareAndSetState - CAS 机制设置 state 状态
    • 独占模式是只有一个线程能够访问资源,而共享模式可以允许多个线程访问资源
  • 提供了基于 FIFO 先进先出的等待队列,类似于 MonitorEntryList
  • 条件变量来实现等待、唤醒机制,支持多个条件变量,类似于 MonitorWaitSet,还有 ReentrantLockCondition

其子类需要自己选择实现下面几个方法中的一部分,因为 AQS 对这几个方法都默认实现了一个抛出 UnsupportedOperationException

  • tryAcquire
  • tryRelease
  • tryAcquireShared
  • tryReleaseShared
  • isHeldExclusively

1.2、自定义不可重入锁

class MyLock implements Lock {
    static class MySync extends AbstractQueuedSynchronizer {
        @Override
        protected boolean tryAcquire(int arg) {
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        @Override
        protected boolean tryRelease(int arg) {
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        @Override
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }

        public ConditionObject newCondition() {
            return new ConditionObject();
        }
    }

    private final MySync sync = new MySync();

    @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();
    }
}

测试

MyLock myLock = new MyLock();
new Thread(() -> {
    myLock.lock();
    log.info("lock...{}", LocalDateTime.now());
    try {
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    myLock.unlock();
    log.info("unlock...{}", LocalDateTime.now());
}).start();
new Thread(() -> {
    myLock.lock();
    log.info("lock...{}", LocalDateTime.now());
    myLock.unlock();
    log.info("unlock...{}", LocalDateTime.now());
}).start();

image-20220212141235996

重复加锁测试

MyLock myLock = new MyLock();
new Thread(() -> {
    myLock.lock();
    log.info("lock...{}", LocalDateTime.now());
    myLock.lock();
    log.info("lock...{}", LocalDateTime.now());
    try {
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    myLock.unlock();
    log.info("unlock...{}", LocalDateTime.now());
}).start();

image-20220212141323679

可以看到是不可重入的,线程就被阻塞住了,只有解开了才能重新加

2、ReentrantLock

image-20220212142452561

2.1、非公平锁

2.1.1、加锁解锁流程

先从构造器开始看,默认为非公平锁实现(谁先抢到谁获得锁)

image-20220212142602403

NonfairSync 继承自 SyncSync 继承自 AQS

static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;

    final void lock() {
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }

    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}
  • 没有竞争的时候,exclusiveOwnerThread 将被标记为 t1
2.1.1.1、加锁失败
  • 此时 t2 来了,开始 CAS 希望将 state0 改为 1,但是失败了,进入 acquire 方法
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
  • 可以看到 acquire 又调用了自身的 tryAcquire 方法,但是此时肯定是失败的,就会进入 acquireQueued 方法,这个方法会先调用 addWaiter 创建一个 Node 节点对象,然后 acquireQueued 加入等待队列中去

  • Node 队列为一个双向链表,且每个节点默认正常状态 waitStatus 为 0,对于此双向链表的头部(head 指向)节点用来占位,不关联线程,称其为 Dummy(哑元)或哨兵

image-20220212152647383

  • 进入 acquireQueued 逻辑方法
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            //拿到当前新创建的Node的前驱Node,现在也就是那个哨兵
            final Node p = node.predecessor();
            //如果自己是紧邻着 head(排第二位),那么再次 tryAcquire 尝试获取锁,当然这时 state 仍为 1,失败
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            //如果失败了,先判断是否需要park,如果需要park,则执行parkAndCheckInterrupt
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
  • 可以看到,代码本身会进行一个死循环,不断的尝试获得锁,失败后会进入 park 阻塞

  • 此时进入 shouldParkAfterFailedAcquire 逻辑,将前驱 node,即 headwaitStatus 改为 -1,这次返回 false其中 SIGNAL = -1 的含义便是,它有责任唤醒其后继节点

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
    //Node.SIGNAL = -1
    if (ws == Node.SIGNAL)
        /*
         * 该节点已经设置了状态,要求释放以发出信号,因此它可以安全地park。
         */
        return true;
    if (ws > 0) {
        /*
         * 前驱节点被取消。跳过前驱并指示重试。
         */
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {//此时 ws = 0
        /*
         * waitStatus 必须为 0 或 PROPAGATE = -3。表示我们需要一个信号,但不要park。呼叫者需要重试以确保在park前无法获取锁。
         */
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}
  • 再次进入循环,再次尝试获取锁,失败,再次进入 shouldParkAfterFailedAcquire ,此时前驱节点已经是 -1 了,直接返回 true,接着调用 parkAndCheckInterrupt,开始阻塞
private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

image-20220212152706112

  • 此时再来一个线程,尝试获得锁,失败,又创建一个 Node 节点进入 acquireQueued 方法,现在其前驱节点不是 head 直接进入 shouldParkAfterFailedAcquire 方法

image-20220212152726905

  • 现在其前驱节点为 t2 状态还是 0 ,所以将其设置为 -1,接着本 Node 再次 park

image-20220212152906537

  • 再次来一个,将会变为这个样子

image-20220212153439080

2.1.1.2、解锁竞争
  • 此时 t1 释放锁,进入 unlock->release->tryRelease 方法,最终就是将状态设置为 0
public void unlock() {
    sync.release(1);
}

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        //头节点已经被创建,且其后继有节点
        if (h != null && h.waitStatus != 0)
            //唤醒其后继节点
            unparkSuccessor(h);
        return true;
    }
    return false;
}

protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

private void unparkSuccessor(Node node) {
    /*
     * If status is negative (i.e., possibly needing signal) try
     * to clear in anticipation of signalling.  It is OK if this
     * fails or if status is changed by waiting thread.
     */
    int ws = node.waitStatus;
    if (ws < 0)
        //改为 0 
        compareAndSetWaitStatus(node, ws, 0);

    /*
     * Thread to unpark is held in successor, which is normally
     * just the next node.  But if cancelled or apparently null,
     * traverse backwards from tail to find the actual
     * non-cancelled successor.
     */
    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        s = null;
        //从最后开始往前找,直到找到 node 节点
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    //将node节点对应的线程唤醒
    if (s != null)
        LockSupport.unpark(s.thread);
}
  • 队列的第一个节点线程,其本身还阻塞在 parkAndCheckInterrupt 方法,唤醒后,就开始重新循环,获得锁
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                //获得锁后,将头节点设置为当前节点,因为上一个节点已经执行完毕了
                setHead(node);
                p.next = null; 
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

image-20220212155636132

  • 如果此时在唤醒 t2 获得锁的过程中,又来了一个线程,其不在阻塞队列中,它直接先抢到了锁,那么 t2 只能重新进入 park 阻塞(非公平的体现

2.2、可重入原理

直接看代码注释

static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;

    final void lock() {
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }

    protected final boolean tryAcquire(int acquires) {
        //来自父类 Sync
        return nonfairTryAcquire(acquires);
    }
}

//父类Sync中的方法
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    //同一个线程来了,肯定是获取不到锁的,如果加锁的线程和当前线程相同,表示发生了锁重入,则进入如下分支
    else if (current == getExclusiveOwnerThread()) {
        //state++ 
        int nextc = c + acquires;
        if (nextc < 0) // 溢出(谁入这么多啊)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

//释放锁的方法,也在父类Sync中
protected final boolean tryRelease(int releases) {
    //释放锁,因为可重入,所以不能直接置为0
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    //如果已经减为0了,才会真正释放锁
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    //只有释放完了才能解锁
    return free;
}

2.3、可打断原理

因为 ReentrantLock 支持设置为不可打断模式 (lock 为不可打断,lockInterruptibly 为可打断模式)

2.3.1、不可打断模式

如果被打断了,就会调用 interrupted 方法,返回是否打断,然后还会重置打断标记

private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                //获得了锁,这时候返回自己是不是被打断过
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                //被打断了,就会将表示设置为true,此标记只有在自己真正获得了锁后才会用到
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        //如果是被打断过,就进入此方法
        selfInterrupt();
}

static void selfInterrupt() {
    Thread.currentThread().interrupt();
}

可以发现在此模式下,即使它被打断,仍会驻留在 AQS 队列中,一直要等到获得锁后方能得知自己被打断了

2.3.2、可打断模式

当调用 lockInterruptibly 方法后:

public void lockInterruptibly() throws InterruptedException {
    sync.acquireInterruptibly(1);
}

public final void acquireInterruptibly(int arg)
    throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    if (!tryAcquire(arg))
        //如果没有获得锁,进入此方法
        doAcquireInterruptibly(arg);
}

private void doAcquireInterruptibly(int arg)
    throws InterruptedException {
    final Node node = addWaiter(Node.EXCLUSIVE);
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                //同样的地方,只不过如果被打断了,直接就抛出异常,停止 AQS 队列中继续等了,直接跳出死循环
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

2.4、公平锁

对于非公平锁,调用 tryAcquire 后,直接就开始抢锁了,没有管 AQS 队列中的其他线程

static final class FairSync extends Sync {
    private static final long serialVersionUID = -3000897897090466540L;

    final void lock() {
        acquire(1);
    }

    /**
     * Fair version of tryAcquire.  Don't grant access unless
     * recursive call or no waiters or is first.
     */
    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        //如果还没有占用
        if (c == 0) {
            //在占用锁之前,先判断是否有前驱节点,没有才会去竞争
            if (!hasQueuedPredecessors() &&
                compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }
}

public final boolean hasQueuedPredecessors() {
    // The correctness of this depends on head being initialized
    // before tail and on head.next being accurate if the current
    // thread is first in queue.
    //尾巴
    Node t = tail; // Read fields in reverse initialization order
    //头
    Node h = head;
    Node s;
    //头不等于尾,证明队列中有Node
    return h != t &&
        //表示队列中还没有老二
        ((s = h.next) == null ||
         //或者队列中老二线程不是此线程
         s.thread != Thread.currentThread());
}

2.5、条件变量ConditionObject

每个条件变量其实就对应着一个等待队列,其实现类是 ConditionObject

2.5.1、await

调用 await 便会调用 addConditionWaiter 创建一个新的 Node 节点

public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    //创建一个新的 Node
    Node node = addConditionWaiter();
    //为什么叫fully,因为锁是可以重入的,所以需要把所有的锁都释放掉
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    while (!isOnSyncQueue(node)) {
        LockSupport.park(this);
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}

private Node addConditionWaiter() {
    Node t = lastWaiter;
    // If lastWaiter is cancelled, clean out.
    if (t != null && t.waitStatus != Node.CONDITION) {
        unlinkCancelledWaiters();
        t = lastWaiter;
    }
    //Node.CONDITION = -2
    Node node = new Node(Thread.currentThread(), Node.CONDITION);
    if (t == null)
        firstWaiter = node;
    else
        //尾插法
        t.nextWaiter = node;
    lastWaiter = node;
    return node;
}
  • 一开始 t1 持有锁,然后调用 await 方法,进入 addConditionWaiter 方法,创建新的 Node ,状态为 -2,并将其关联到 t1,加入等待队列尾部

image-20220212165525428

  • 然后调用 fullyRelease 方法,为什么叫 fully,因为锁是可以重入的,所以需要把所有的锁都释放掉
final int fullyRelease(Node node) {
    boolean failed = true;
    try {
        //拿到总state
        int savedState = getState();
        if (release(savedState)) {
            failed = false;
            return savedState;
        } else {
            throw new IllegalMonitorStateException();
        }
    } finally {
        if (failed)
            node.waitStatus = Node.CANCELLED;
    }
}

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            //唤醒阻塞队列中的其他节点
            unparkSuccessor(h);
        return true;
    }
    return false;
}

image-20220212170033476

  • 如果没有其他线程竞争锁,那么应该是 t2 线程获得锁

image-20220212170226274

2.5.2、signal

现在,t2 要唤醒刚刚的 t2,会将当前等待队列的头节点转移到 AQS 等待队列中,如果转移失败,证明当前节点已经被取消执行,会继续找下一个节点,如果转移成功,就会解锁,让等待队列的其余节点开始竞争锁

public final void signal() {
    //判断当前线程是否持有锁
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    //找到等待队列的头节点
    Node first = firstWaiter;
    if (first != null)
        doSignal(first);
}

private void doSignal(Node first) {
    do {
        //将头节点去掉
        if ( (firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        first.nextWaiter = null;
        //会将当前头节点转移到竞争锁的队列中,如果转移失败,就会继续找下一个节点
    } while (!transferForSignal(first) &&
             (first = firstWaiter) != null);
}

final boolean transferForSignal(Node node) {
    /*
     * If cannot change waitStatus, the node has been cancelled.
     * 如果不能被转移,那就证明这个任务已经被取消了,就没有必要转移了
     */
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        return false;

    /*
     * Splice onto queue and try to set waitStatus of predecessor to
     * indicate that thread is (probably) waiting. If cancelled or
     * attempt to set waitStatus fails, wake up to resync (in which
     * case the waitStatus can be transiently and harmlessly wrong).
     */
    Node p = enq(node);
    int ws = p.waitStatus;
  	//设置前一个节点状态为-1
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        //执行解锁操作
        LockSupport.unpark(node.thread);
    return true;
}

image-20220212171243151

3、读写锁

3.1、ReentrantReadWriteLock使用

当读操作远远高于写操作时,这时候使用 读写锁 让 读-读 可以并发,提高性能。 类似于数据库中的 select … from … lock in share mode ,简单编写一个测试类

@Slf4j
class DataContainer {
    private Object data;
    private final ReentrantReadWriteLock rw = new ReentrantReadWriteLock();
    private final ReentrantReadWriteLock.ReadLock r = rw.readLock();
    private final ReentrantReadWriteLock.WriteLock w = rw.writeLock();

    @SneakyThrows
    public Object read() {
        log.info("获取读锁...{}", LocalDateTime.now());
        r.lock();
        try {
            log.info("读取 {}", LocalDateTime.now());
            TimeUnit.SECONDS.sleep(1);
            return data;
        } finally {
            log.info("释放读锁...{}", LocalDateTime.now());
            r.unlock();
        }
    }

    @SneakyThrows
    public void write() {
        log.info("获取写锁...{}", LocalDateTime.now());
        w.lock();
        try {
            log.info("写入 {}", LocalDateTime.now());
            TimeUnit.SECONDS.sleep(1);
        } finally {
            log.info("释放写锁...{}", LocalDateTime.now());
            w.unlock();
        }
    }
}
  • 测试 读-读
DataContainer dataContainer = new DataContainer();
new Thread(dataContainer::read, "t1").start();
new Thread(dataContainer::read, "t2").start();

image-20220212172505924

可以发现,读读操作是畅通无阻的

  • 测试 读-写
DataContainer dataContainer = new DataContainer();
new Thread(dataContainer::read, "t1").start();
new Thread(dataContainer::write, "t2").start();

image-20220212172653954

可以发现,读写操作,是互斥的

  • 测试 写-写
DataContainer dataContainer = new DataContainer();
new Thread(dataContainer::write, "t1").start();
new Thread(dataContainer::write, "t2").start();

image-20220212172817817

写写,也是互斥的

注意:

  • 读锁不支持条件变量
  • 重入时升级不支持(即持有读锁的情况下去获取写锁,会导致获取写锁永久等待),因为读锁不能保证会有其他的读锁✌️
  • 但是支持降级(也就是持有写锁的时候,再去获取读锁)

3.2、读写锁原理

读写锁用的是同一个 Sync 同步器,因此等待队列、state 等也是同一个

  • t1 写,t2

  • t1 成功上写锁,流程与 ReentrantLock 加锁相比没有特殊之处,不同是写锁状态占了 state 的低 16 位,而读锁使用的是 state 的高 16 位

image-20220212180024555

//写锁的方法
public void lock() {
    sync.acquireShared(1);
}

//这个方法熟,就是之前看的 AQS 方法
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

//ReentrantReadWriteLock重写的方法
protected final boolean tryAcquire(int acquires) {
    /*
     * Walkthrough:
     * 1. If read count nonzero or write count nonzero
     *    and owner is a different thread, fail.
     * 2. If count would saturate, fail. (This can only
     *    happen if count is already nonzero.)
     * 3. Otherwise, this thread is eligible for lock if
     *    it is either a reentrant acquire or
     *    queue policy allows it. If so, update state
     *    and set owner.
     */
    Thread current = Thread.currentThread();
    int c = getState();
    //写锁部分
    int w = exclusiveCount(c);
    //不为0,可能是其他线程加了读锁,也有可能是加了写锁
    if (c != 0) {
        // (Note: if c != 0 and w == 0 then shared count != 0)
        //如果写锁等于0,那表示之前已经加了读锁,如果家的就是写锁,再判断是不是自己加的(重入了)
        if (w == 0 || current != getExclusiveOwnerThread())
            //读写互斥,直接返回false
            return false;
        //如果写锁加上1超过最大值了(16 位 65535),则重入次数太多了
        if (w + exclusiveCount(acquires) > MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        // Reentrant acquire
        //写锁基础上+1,重入了
        setState(c + acquires);
        return true;
    }
    //如果没有加锁,那我就来,首先判断当前线程是否要阻塞,如果是非公平锁,总是返回false,就需要等待
    if (writerShouldBlock() ||
        !compareAndSetState(c, c + acquires))
        return false;
    //设置自己为Owner线程
    setExclusiveOwnerThread(current);
    return true;
}
  • t2 这时候来了,执行读锁的 lock 方法,这时进入读锁的 sync.acquireShared(1) 流程,首先会进入 tryAcquireShared 流程。如果有写锁占据,那么 tryAcquireShared 返回 -1 表示失败
    • -1 表示失败
    • 0 表示成功,但后继节点不会继续唤醒==(读写锁不涉及)==
    • 正数表示成功,而且数值是还有几个后继节点需要唤醒,读写锁返回 1
//读锁重写的方法
public void lock() {
    sync.acquireShared(1);
}

public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}

protected final int tryAcquireShared(int unused) {
    /*
     * Walkthrough:
     * 1. If write lock held by another thread, fail.
     * 2. Otherwise, this thread is eligible for
     *    lock wrt state, so ask if it should block
     *    because of queue policy. If not, try
     *    to grant by CASing state and updating count.
     *    Note that step does not check for reentrant
     *    acquires, which is postponed to full version
     *    to avoid having to check hold count in
     *    the more typical non-reentrant case.
     * 3. If step 2 fails either because thread
     *    apparently not eligible or CAS fails or count
     *    saturated, chain to version with full retry loop.
     */
    Thread current = Thread.currentThread();
    int c = getState();
    //检验写锁是否为0
    if (exclusiveCount(c) != 0 &&
        //前一个判断已经加了写锁了,然后加读锁的就是自己,又来加读锁,应该是允许的,否则就 -1
        getExclusiveOwnerThread() != current)
        //这里直接就返回 -1 
        return -1;
    //
    int r = sharedCount(c);
    if (!readerShouldBlock() &&
        r < MAX_COUNT &&
        compareAndSetState(c, c + SHARED_UNIT)) {
        if (r == 0) {
            firstReader = current;
            firstReaderHoldCount = 1;
        } else if (firstReader == current) {
            firstReaderHoldCount++;
        } else {
            HoldCounter rh = cachedHoldCounter;
            if (rh == null || rh.tid != getThreadId(current))
                cachedHoldCounter = rh = readHolds.get();
            else if (rh.count == 0)
                readHolds.set(rh);
            rh.count++;
        }
        return 1;
    }
    return fullTryAcquireShared(current);
}
  • 如果加锁失败就会进入 doAcquireShared 这个方法,首先也是调用 addWaiter 添加节点,不同之处在于节点被设置为 Node.SHARED 模式(读锁共享)而非 Node.EXCLUSIVE 模式(写锁独占),注意此时 t2 仍处于活跃状态

image-20220212192721877

//如果加锁失败就会进入这个方法
private void doAcquireShared(int arg) {
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            //获得前驱节点
            final Node p = node.predecessor();
            //如果自己是老二,则还有权获得锁
            if (p == head) {
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    if (interrupted)
                        selfInterrupt();
                    failed = false;
                    return;
                }
            }
            //当前线程是否需要park,如果park了,就让前驱节点的状态改为-1,有责任唤醒当前线程,然后返回false,进入下一次循环
            if (shouldParkAfterFailedAcquire(p, node) &&
                //第二次进来后park
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

image-20220212193514827

  • t3t4

  • 现在这种状态下,假设又来了 t3 加读锁,t4 加写锁,这期间 t1 仍然持有锁,就变成下面的样子

image-20220212194317774

  • 现在 t1 准备解锁,执行 unlock -> release -> tryRelease
public void unlock() {
    sync.release(1);
}

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            //开始唤醒流程
            unparkSuccessor(h);
        return true;
    }
    return false;
}

protected final boolean tryRelease(int releases) {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    int nextc = getState() - releases;
    //判断是否重入
    boolean free = exclusiveCount(nextc) == 0;
    if (free)
        setExclusiveOwnerThread(null);
    setState(nextc);
    return free;
}

image-20220212194848001

  • 释放锁后,调用 unparkSuccessor 方法开启唤醒流程,这时 t2 恢复运行,从 parkAndCheckInterrupt 处恢复运行,重新执行 for 循环,然后获得锁
  • 然后调用 setHeadAndPropagate 方法,此方法将 t2 原本的节点设置为头节点
private void doAcquireShared(int arg) {
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head) {
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    //	
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    if (interrupted)
                        selfInterrupt();
                    failed = false;
                    return;
                }
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

image-20220212200637099

  • setHeadAndPropagate 方法中还会检查下一个节点是否是 shared,如果是则调用 doReleaseShared()head 的状态从 -1 改为 0 并唤醒之(先改为 0 是为了避免其他线程也执行到这里,做相同的操作),此时 t2t3 都处于活跃状态,t3 回来后又执行了 tryAcquireShared 方法,读锁还会加 1
private void setHeadAndPropagate(Node node, int propagate) {
    Node h = head; // Record old head for check below
    setHead(node);
    /*
     * Try to signal next queued node if:
     *   Propagation was indicated by caller,
     *     or was recorded (as h.waitStatus either before
     *     or after setHead) by a previous operation
     *     (note: this uses sign-check of waitStatus because
     *      PROPAGATE status may transition to SIGNAL.)
     * and
     *   The next node is waiting in shared mode,
     *     or we don't know, because it appears null
     *
     * The conservatism in both of these checks may cause
     * unnecessary wake-ups, but only when there are multiple
     * racing acquires/releases, so most need signals now or soon
     * anyway.
     */
    if (propagate > 0 || h == null || h.waitStatus < 0 ||
        (h = head) == null || h.waitStatus < 0) {
        Node s = node.next;
        if (s == null || s.isShared())
            doReleaseShared();
    }
}

image-20220212201508002

此时 t3 又执行到了 setHeadAndPropagate,相同的逻辑,t3 节点被置为头节点,其后方的 t4 不再是共享了,所以停止唤醒

image-20220212201119308

  • t2 解锁,t3 解锁

  • t2 进入 sync.releaseShared(1) 中,调用 tryReleaseShared(1) 让计数减一,但此时计数还不为零,t3 再次解锁才能减为 0

public void unlock() {
    sync.releaseShared(1);
}

public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        //只有减为0才能全部释放
        doReleaseShared();
        return true;
    }
    return false;
}
  • 进入 doReleaseShared 方法完整释放锁
private void doReleaseShared() {
    /*
     * Ensure that a release propagates, even if there are other
     * in-progress acquires/releases.  This proceeds in the usual
     * way of trying to unparkSuccessor of head if it needs
     * signal. But if it does not, status is set to PROPAGATE to
     * ensure that upon release, propagation continues.
     * Additionally, we must loop in case a new node is added
     * while we are doing this. Also, unlike other uses of
     * unparkSuccessor, we need to know if CAS to reset status
     * fails, if so rechecking.
     */
    for (;;) {
        Node h = head;
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            if (ws == Node.SIGNAL) {
                //避免其他线程操作
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            // loop to recheck cases
                //唤醒写锁
                unparkSuccessor(h);
            }
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        if (h == head)                   // loop if head changed
            break;
    }
}

3.3、StampedLock

该类自 JDK 8 加入,是为了进一步优化读性能,它的特点是在使用读锁、写锁时都必须配合【戳】使用

  • 加解读锁
long stamp = lock.readLock();
lock.unlockRead(stamp);
  • 加解写锁
long stamp = lock.writeLock();
lock.unlockWrite(stamp);

乐观读,StampedLock 支持 tryOptimisticRead() 方法(乐观读),读取完毕后需要做一次戳校验,如果校验通过,表示这期间确实没有写操作,数据可以安全使用,如果校验没通过,需要重新获取读锁,保证数据安全。

long stamp = lock.tryOptimisticRead();
// 验戳
if(!lock.validate(stamp)){
    // 锁升级
}

编写一个数据容器类,内部分别使用读锁保护数据的 read() 方法,写锁保护数据的 write() 方法

@Slf4j
class DataContainerStamped {
    private int data;
    private final StampedLock lock = new StampedLock();

    public DataContainerStamped(int data) {
        this.data = data;
    }

    @SneakyThrows
    public int read(int readTime) {
        long stamp = lock.tryOptimisticRead();
        log.info("optimistic read locking...{},{}", stamp, LocalDateTime.now());
        TimeUnit.SECONDS.sleep(readTime);
        if (lock.validate(stamp)) {
            log.info("read finish...{}, data:{},{}", stamp, data, LocalDateTime.now());
            return data;
        }
        // 锁升级 - 读锁
        log.info("updating to read lock... {},{}", stamp, LocalDateTime.now());
        try {
            stamp = lock.readLock();
            log.info("read lock {},{}", stamp, LocalDateTime.now());
            TimeUnit.SECONDS.sleep(readTime);
            log.info("read finish...{}, data:{},{}", stamp, data, LocalDateTime.now());
            return data;
        } finally {
            log.info("read unlock {},{}", stamp, LocalDateTime.now());
            lock.unlockRead(stamp);
        }
    }

    @SneakyThrows
    public void write(int newData) {
        long stamp = lock.writeLock();
        log.info("write lock {},{}", stamp, LocalDateTime.now());
        try {
            TimeUnit.SECONDS.sleep(2);
            this.data = newData;
        } finally {
            log.info("write unlock {},{}", stamp, LocalDateTime.now());
            lock.unlockWrite(stamp);
        }
    }
}

读读

DataContainerStamped dataContainer = new DataContainerStamped(1);
new Thread(() -> {
    dataContainer.read(1);
}, "t1").start();
TimeUnit.MILLISECONDS.sleep(500);
new Thread(() -> {
    dataContainer.read(0);
}, "t2").start();

image-20220212205352215

读写

DataContainerStamped dataContainer = new DataContainerStamped(1);
new Thread(() -> {
    dataContainer.read(1);
}, "t1").start();
TimeUnit.MILLISECONDS.sleep(500);
new Thread(() -> {
    dataContainer.write(100);
}, "t2").start();

注意:StampedLock 不支持条件变量,不支持可重入

4、Semaphore

信号量,用来限制能同时访问共享资源的线程上限。

4.1、基本使用

public Semaphore(int permits) {
    sync = new NonfairSync(permits);
}

public Semaphore(int permits, boolean fair) {
    sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}

可以看到,如果是调用不带布尔参数的构造方法,默认是一个非公平锁(不管先后,谁抢到谁获得锁),如果带了且为 True ,则创建的就是一个公平锁(严格按照先来后到)

// 1. 创建 semaphore 对象
Semaphore semaphore = new Semaphore(3);
// 2. 10个线程同时运行
for (int i = 0; i < 10; i++) {
    new Thread(() -> {
        // 3. 获取许可
        try {
            semaphore.acquire();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            log.info("running...");
            TimeUnit.SECONDS.sleep(1);
            log.info("end...");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 4. 释放许可
            semaphore.release();
        }
    }).start();
}

image-20220213095719212

4.2、应用

在学习享元模式的时候,我们使用了 wait notify 实现了一个伪数据库连接池

@Slf4j
public class Test {

    @SneakyThrows
    public static void main(String[] args) {
        MockPool mockPool = new MockPool(5);
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                MockConnection connection = mockPool.getConnection();
                System.out.println("拿到连接:" + connection.getConnectionName());
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("归还连接:" + connection.getConnectionName());
                mockPool.free(connection);
            }).start();
        }
    }
}

final class MockPool {
    private final MockConnection[] connections;
    private final AtomicIntegerArray state;
    private static final Integer DEFAULT_POOL_SIZE = 5;

    public MockPool() {
        connections = new MockConnection[DEFAULT_POOL_SIZE];
        state = new AtomicIntegerArray(DEFAULT_POOL_SIZE);
        initPool();
    }

    public MockPool(Integer poolSize) {
        connections = new MockConnection[poolSize];
        state = new AtomicIntegerArray(poolSize);
        initPool();
    }

    private void initPool() {
        for (int i = 0; i < connections.length; i++) {
            connections[i] = new MockConnection("连接-" + (i + 1));
        }
    }

    @SneakyThrows
    public MockConnection getConnection() {
        for (; ; ) {
            for (int i = 0; i < connections.length; i++) {
                if (state.get(1) == 0) {
                    if (state.compareAndSet(i, 0, 1)) {
                        return connections[i];
                    }
                }
            }
            synchronized (this) {
                System.out.println("无可用连接,等待...");
                wait();
            }
        }
    }

    public void free(MockConnection connection) {
        for (int i = 0; i < connections.length; i++) {
            if (connections[i] == connection) {
                state.set(i, 0);
                synchronized (this) {
                    notifyAll();
                }
                return;
            }
        }
        System.out.println("要释放的连接不存在");
    }
}

@Data
@NoArgsConstructor
@AllArgsConstructor
class MockConnection {
    private String connectionName;
}

wait notify 偏底层了,对于初学者不易掌握,容易出错,所以我们使用 Semaphore 改进

/**
 * @author 两米以下皆凡人
 */
@Slf4j
public class Test {

    @SneakyThrows
    public static void main(String[] args) {
        MockPool mockPool = new MockPool(5);
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                MockConnection connection = mockPool.getConnection();
                System.out.println("拿到连接:" + connection.getConnectionName());
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("归还连接:" + connection.getConnectionName());
                mockPool.free(connection);
            }).start();
        }
    }
}

final class MockPool {
    private final MockConnection[] connections;
    private final AtomicIntegerArray state;
    private static final Integer DEFAULT_POOL_SIZE = 5;
    private final Semaphore semaphore;

    public MockPool() {
        connections = new MockConnection[DEFAULT_POOL_SIZE];
        state = new AtomicIntegerArray(DEFAULT_POOL_SIZE);
        semaphore = new Semaphore(DEFAULT_POOL_SIZE);
        initPool();
    }

    public MockPool(Integer poolSize) {
        connections = new MockConnection[poolSize];
        state = new AtomicIntegerArray(poolSize);
        semaphore = new Semaphore(poolSize);
        initPool();
    }

    private void initPool() {
        for (int i = 0; i < connections.length; i++) {
            connections[i] = new MockConnection("连接-" + (i + 1));
        }
    }

    @SneakyThrows
    public MockConnection getConnection() {
        semaphore.acquire();
        for (int i = 0; i < connections.length; i++) {
            if (state.get(i) == 0) {
                if (state.compareAndSet(i, 0, 1)) {
                    return connections[i];
                }
            }
        }
        //一定不会执行到这里
        return null;
    }

    public void free(MockConnection connection) {
        for (int i = 0; i < connections.length; i++) {
            if (connections[i] == connection) {
                state.set(i, 0);
                semaphore.release();
                return;
            }
        }
        System.out.println("要释放的连接不存在");
    }
}

@Data
@NoArgsConstructor
@AllArgsConstructor
class MockConnection {
    private String connectionName;
}

4.3、原理

4.3.1、加锁解锁流程

Semaphore 有点像一个停车场,permits 就好像停车位数量,当线程获得了 permits 就像是获得了停车位,然后 停车场显示空余车位减一 ,刚开始,**permits(state)**为 3,这时 5 个线程来获取资源

static final class NonfairSync extends Sync {
    private static final long serialVersionUID = -2694183684443567898L;

    NonfairSync(int permits) {
        super(permits);
    }

    protected int tryAcquireShared(int acquires) {
        return nonfairTryAcquireShared(acquires);
    }
}

//实际上把 permits 存入了 state中
Sync(int permits) {
    setState(permits);
}

image-20220213104408611

  • 假设 t1 t2 t4 竞争锁成功,t3 t5 竞争失败,进入 AQS 阻塞队列 park
public void acquire() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}

public final void acquireSharedInterruptibly(int arg)
    throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    //只要获取锁成功,都是正数,第四次就会判断成功,进入下面方法
    if (tryAcquireShared(arg) < 0)
        doAcquireSharedInterruptibly(arg);
}

final int nonfairTryAcquireShared(int acquires) {
    for (;;) {
        int available = getState();
        //前三次没有问题,第四次来了后,减为了-1
        int remaining = available - acquires;
        if (remaining < 0 ||
            compareAndSetState(available, remaining))
            //直接就返回了-1
            return remaining;
    }
}

private void doAcquireSharedInterruptibly(int arg)
    throws InterruptedException {
    //创建节点
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();
            //老二再试一次
            if (p == head) {
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
            }
            //设置为 -1
            if (shouldParkAfterFailedAcquire(p, node) &&
                //park住
                parkAndCheckInterrupt())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

image-20220213105606989

  • 这时 t4 释放 Semaphore
public void release() {
    sync.releaseShared(1);
}

public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}

protected final boolean tryReleaseShared(int releases) {
    for (;;) {
        int current = getState();
        //0 + 1 = 1
        int next = current + releases;
        if (next < current) // 溢出
            throw new Error("Maximum permit count exceeded");
        if (compareAndSetState(current, next))
            return true;
    }
}

private void doReleaseShared() {
    for (;;) {
        Node h = head;
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            //是否要唤醒后继
            if (ws == Node.SIGNAL) {
                //避免其他线程干扰
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            // loop to recheck cases
                //唤醒后继
                unparkSuccessor(h);
            }
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        if (h == head)                   // loop if head changed
            break;
    }
}
  • 现在唤醒头节点的下一个节点 t3,重新进入 for 循环,获取锁,然后执行 setHeadAndPropagate 方法,唤醒后面的所有共享节点,但是没有用,因为是 t3 自己先获取了再唤醒的,此时还是为 0,所以 t5 再次 park

5、CountDownLatch

用来进行线程同步协作,等待所有线程完成倒计时。 其中构造参数用来初始化等待计数值,await() 用来等待计数归零,countDown() 用来让计数减一

CountDownLatch latch = new CountDownLatch(3);
new Thread(() -> {
    log.info("begin...");
    try {
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    latch.countDown();
    log.info("end...{}", latch.getCount());
}).start();
new Thread(() -> {
    log.info("begin...");
    try {
        TimeUnit.SECONDS.sleep(2);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    latch.countDown();
    log.info("end...{}", latch.getCount());
}).start();
new Thread(() -> {
    log.info("begin...");
    try {
        TimeUnit.MILLISECONDS.sleep(1500);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    latch.countDown();
    log.info("end...{}", latch.getCount());
}).start();
log.info("waiting...");
latch.await();
log.info("wait end...");

image-20220213110749151

其实它应该算是 JUC 中最简单的一个工具了,源码如下,有了前面各种工具的底层原理分析,它很容易就读明白的

public class CountDownLatch {
 
    private static final class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 4982264981922014374L;

        Sync(int count) {
            setState(count);
        }

        int getCount() {
            return getState();
        }

        protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
        }

        protected boolean tryReleaseShared(int releases) {
            for (;;) {
                int c = getState();
                if (c == 0)
                    return false;
                int nextc = c-1;
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }
    }

    private final Sync sync;

    public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }

    public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

    public boolean await(long timeout, TimeUnit unit)
        throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    }

    public void countDown() {
        sync.releaseShared(1);
    }

    public long getCount() {
        return sync.getCount();
    }

    public String toString() {
        return super.toString() + "[Count = " + sync.getCount() + "]";
    }
}

就目前实现的 Demo 我们发现,其效果和 join 的效果一样,那计数器存在的意义在哪,其实,真实场景下,我们的线程都是从线程池中获取的,从而达到线程重用,某些固定大小的线程池,其中的线程还会一直的运行,并不会轻易结束,所以就不能使用 join 这种底层的 API 去实现,下面实现一个使用线程池来倒计时的简单应用

CountDownLatch latch = new CountDownLatch(3);
ExecutorService service = Executors.newFixedThreadPool(4);
service.submit(() -> {
    log.info("begin...");
    try {
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    latch.countDown();
    log.info("end...{}", latch.getCount());
});
service.submit(() -> {
    log.info("begin...");
    try {
        TimeUnit.MILLISECONDS.sleep(1500);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    latch.countDown();
    log.info("end...{}", latch.getCount());
});
service.submit(() -> {
    log.info("begin...");
    try {
        TimeUnit.SECONDS.sleep(2);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    latch.countDown();
    log.info("end...{}", latch.getCount());
});
service.submit(() -> {
    try {
        log.info("waiting...");
        latch.await();
        log.info("wait end...");
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
});

image-20220213111717355

6、CyclicBarrier

6.1、问题提出

  • 在使用 CountDownLatch 的时候,我们在执行过程中,CountDownLatch 创建了一次后,主线程必须等到其减为 0 才能继续运行,现在有一个新的需求,这一次执行我需要重复执行三遍,主线程也必须重复等待三次,通常的做法就是使用 for 循环解决:
ExecutorService service = Executors.newFixedThreadPool(4);
for (int i = 0; i < 3; i++) {
    CountDownLatch latch = new CountDownLatch(3);
    service.submit(() -> {
        log.info("begin...");
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        latch.countDown();
        log.info("end...{}", latch.getCount());
    });
    service.submit(() -> {
        log.info("begin...");
        try {
            TimeUnit.MILLISECONDS.sleep(1500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        latch.countDown();
        log.info("end...{}", latch.getCount());
    });
    service.submit(() -> {
        log.info("begin...");
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        latch.countDown();
        log.info("end...{}", latch.getCount());
    });
    try {
        latch.await();
        log.info("finish,{}", latch.getCount());
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

image-20220213113801357

但是我们发现,每次循环都创建了新的计数器,而计数器本身并没有方法可以直接更改其中的计数,那有没有更好的方式解决这个问题呢

6.2、基本使用

CyclicBarrier:循环栅栏,用来进行线程协作,等待线程满足某个计数。构造时设置『计数个数』,每个线程执行到某个需要“同步”的时刻调用 await() 方法进行等待,当等待的线程数满足『计数个数』时,继续执行

所以上面的问题,我们就可以使用这个工具来解决

// 个数为2时才会继续执行
CyclicBarrier cb = new CyclicBarrier(2, () -> {
    System.out.println("task1 task2 end");
});
for (int i = 0; i < 3; i++) {
    int j = i;
    new Thread(() -> {
        System.out.println("线程" + j + "-1开始.." + new Date());
        try {
            // 当个数不足时,等待
            cb.await();
        } catch (InterruptedException | BrokenBarrierException e) {
            e.printStackTrace();
        }
        System.out.println("线程" + j + "-1继续向下运行..." + new Date());
    }).start();
    new Thread(() -> {
        System.out.println("线程" + j + "-2开始.." + new Date());
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            // 2 秒后,线程个数够2,继续运行
            cb.await();
        } catch (InterruptedException | BrokenBarrierException e) {
            e.printStackTrace();
        }
        System.out.println("线程" + j + "-2继续向下运行..." + new Date());
    }).start();

image-20220213114733563

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值