JDK 源码剖析(二)共享锁

前置知识 —— AQS

AQS 的全称是 AbstractQueuedSynchronizer(抽象队列同步器),是基于 FIFO 队列实现的,并且内部维护了一个状态变量 state,通过原子更新这个状态变量 state 即可以实现加锁解锁操作。
简单点来说就是加锁失败就会进入一个先进先出的队列中阻塞等待,如果锁被释放会唤醒队列中等待的线程去重新执行加锁逻辑。

内部类与属性

内部类 Node:

static final class Node {
    // 标识节点是共享模式下的节点
    static final Node SHARED = new Node();
    // 标识节点是独占模式下的节点
    static final Node EXCLUSIVE = null;

    // 标识线程已取消
    static final int CANCELLED =  1;
    // 标识后继节点需要唤醒
    static final int SIGNAL    = -1;
    // 标识线程等待在一个条件上
    static final int CONDITION = -2;
    // 标识后面的共享锁需要无条件的传播(共享锁需要连续唤醒读的线程)
    static final int PROPAGATE = -3;
    
    // 当前节点保存的线程对应的等待状态
    volatile int waitStatus;

    // 前一个节点
    volatile Node prev;
    
    // 后一个节点
    volatile Node next;

    // 当前节点保存的线程
    volatile Thread thread;

    // 下一个等待在条件上的节点(Condition 锁时使用)
    Node nextWaiter;
	
	//......
}

典型的双链表结构,节点中保存着当前线程、前一个节点、后一个节点以及线程的状态等信息。
主要属性:

// 队列的头节点
private transient volatile Node head;
// 队列的尾节点
private transient volatile Node tail;
// 控制锁的状态变量,等于 0 时表示没有线程占有锁,等于 1 时表示已经有人占用锁
private volatile int state;

image-20200913180957570

需要子类实现的方法

AQS 本质上是一个抽象类,说明它本质上应该是需要子类来实现的,它提供了以下方法给子类去实现:

// 独占模式下使用:尝试获取锁
protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}
// 独占模式下使用:尝试释放锁
protected boolean tryRelease(int arg) {
    throw new UnsupportedOperationException();
}
// 共享模式下使用:尝试获取锁
protected int tryAcquireShared(int arg) {
    throw new UnsupportedOperationException();
}
// 共享模式下使用:尝试释放锁
protected boolean tryReleaseShared(int arg) {
    throw new UnsupportedOperationException();
}
// 判断当前线程是否独占着锁
protected boolean isHeldExclusively() {
    throw new UnsupportedOperationException();
}

CountDownLatch

CountDownLatch 基础

CountDownLatch:允许一个或多个线程等待其它线程的操作执行完毕后再执行后续的操作。

运用场景:我们有一个主线程和 5 个辅助线程,等待主线程准备就绪了,5个辅助线程开始运行,等待 5 个辅助线程运行完毕了,主线程继续往下运行。‘

public class CountDownLatchDemo {

    public static void main(String[] args) throws InterruptedException {
        CountDownLatch startSignal = new CountDownLatch(1);
        CountDownLatch doneSignal = new CountDownLatch(5);

        for (int i = 0; i < 5; i++) {
            new Thread(()->{
                try {
                    System.out.println("辅助线程-" + Thread.currentThread().getName() + "正在执行任务...");
                    startSignal.await();
                    System.out.println("辅助线程-" + Thread.currentThread().getName() + "任务完成...");
                    doneSignal.countDown();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }

        Thread.sleep(2000);
        System.out.println("主线程正在运行...");
        startSignal.countDown();

        System.out.println("主线程等待辅助线程完成任务...");
        doneSignal.await();

        System.out.println("主线程继续运行...");
    }

}

输出结果:

辅助线程-Thread-0正在执行任务...
辅助线程-Thread-2正在执行任务...
辅助线程-Thread-1正在执行任务...
辅助线程-Thread-3正在执行任务...
辅助线程-Thread-4正在执行任务...
主线程正在运行...
主线程等待辅助线程完成任务...
辅助线程-Thread-3任务完成...
辅助线程-Thread-0任务完成...
辅助线程-Thread-2任务完成...
辅助线程-Thread-1任务完成...
辅助线程-Thread-4任务完成...
主线程继续运行...

这段代码分成两段:

第一段,5 个辅助线程等待开始的信号,信号由主线程发出,所以 5 个辅助线程调用 startSignal.await() 方法等待开始信号,当主线程的事儿干完了,调用startSignal.countDown() 通知辅助线程开始干活。

第二段,主线程等待 5 个辅助线程完成的信号,信号由 5 个辅助线程发出,所以主线程调用 doneSignal.await() 方法等待完成信号,5 个辅助线程干完自己的活儿的时候调用 doneSignal.countDown() 方法发出自己的完成的信号,当完成信号达到5个的时候,唤醒主线程继续执行后续的逻辑。

比如我们去游乐园坐激流勇进,有的时候游乐园里人不是那么多,这时,管理员会让你稍等一下,等人坐满了再开船,这样的话可以在一定程度上节约游乐园的成本。座位有多少,就需要等多少人,这就是 CountDownLatch 的核心思想,等到一个设定的数值达到之后,才能出发。

内部类和构造方法

内部类 Sync:

private static final class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = 4982264981922014374L;
    
    Sync(int count) {
        setState(count);
    }
    int getCount() {
        return getState();
    }
    // 实现 AQS 需要子类实现的尝试获取共享锁方法
    protected int tryAcquireShared(int acquires) {
        // state 不等于 0 时返回 -1,state 是通过构造函数传入的 count 设置初始值的
        // 也就是说 count 不为 0 的时候总是要排队,返回 -1
        return (getState() == 0) ? 1 : -1;
    }

    // 实现 AQS 需要子类实现的尝试释放共享锁方法
    protected boolean tryReleaseShared(int releases) {
        for (;;) {
            int c = getState();
            if (c == 0)
                return false;
            int nextc = c-1;
            // 将 state 减到 0 时,会返回true,这时会唤醒排队的线程
            if (compareAndSetState(c, nextc))
                return nextc == 0;
        }
    }
}

构造方法:

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

await() 方法

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

调用 AQS acquireSharedInterruptibly 方法尝试获取共享锁。

public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    // 尝试获取共享锁,如果失败则排队
    if (tryAcquireShared(arg) < 0)
        doAcquireSharedInterruptibly(arg);
}

tryAcquireShared 逻辑在 CountDownLatch 类的内部类 Sync 中实现,代码如下:

    // 实现 AQS 需要子类实现的尝试获取共享锁方法
    protected int tryAcquireShared(int acquires) {
        // state 不等于 0 时返回 -1,state 是通过构造函数传入的 count 设置初始值的
        // 也就是说 count 不为 0 的时候总是要排队,返回 -1
        return (getState() == 0) ? 1 : -1;
    }

入队逻辑如下:

private void doAcquireSharedInterruptibly(int arg)
    throws InterruptedException {
    // 节点入队
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        for (;;) {
            // 获取当前节点的前驱节点
            final Node p = node.predecessor();
            // 如果前驱节点为 head 节点,说明有获取锁的资格,尝试获取锁
            if (p == head) {
                int r = tryAcquireShared(arg);
                if (r >= 0) {	// 如果 count == 0 时,r == 1,进入 if 逻辑
                    // 设置当前节点为头结点,并且将节点状态设置为 Propagate(传播)
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
            }
            // 判断节点是否需要挂起,讲解独占锁的时候已经解释过
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw new InterruptedException();
        }
    } finally {	// 节点被取消,讲解独占锁的时候已经解释过
        if (failed)
            cancelAcquire(node);
    }
}

await() 方法是等待其它线程完成的方法,它会先尝试获取一下共享锁,如果失败则进入 AQS 的队列中排队等待被唤醒。

countDown() 方法

public void countDown() {
    // 调用AQS的释放共享锁方法
    sync.releaseShared(1);
}

releaseShared 是 AQS 留给子类实现释放共享锁的方法,代码逻辑如下:

public final boolean releaseShared(int arg) {
    // 尝试释放共享锁,如果成功了,就唤醒排队的线程
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}

CountDownLatch 内部类 Sync 已经实现了 tryReleaseShared 方法,代码如下:

    // 实现 AQS 需要子类实现的尝试释放共享锁方法
    protected boolean tryReleaseShared(int releases) {
        for (;;) {
            int c = getState();
            if (c == 0)
                return false;
            int nextc = c-1;
            // 将 state 减到 0 时,会返回true,这时会唤醒排队的线程
            if (compareAndSetState(c, nextc))
                return nextc == 0;
        }
    }

如果 nextc == 0,即 state == 0 时,tryReleaseShared 返回 true。此时会调用 doReleaseShared() 方法,代码如下:

private void doReleaseShared() {
    for (;;) {
        Node h = head;
        // head 不为 null && head != tail。 即阻塞队列不为空
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            // 如果头节点状态为 SIGNAL,唤醒后继节点
            if (ws == Node.SIGNAL) {
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            // loop to recheck cases
                unparkSuccessor(h);
            }
            // 把头节点的状态改为 PROPAGATE 成功才会跳到下面的 if
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        // 如果唤醒后 head 没变,则跳出循环
        if (h == head)                   // loop if head changed
            break;
    }
}

通过独占锁的分析,随着多个线程入队,会将 head 状态设置为 SIGNAL,所以会唤醒队列中的第一个节点。

再看下之前 await() 方法执行到 doAcquireSharedInterruptibly 的逻辑,代码如下:

private void doAcquireSharedInterruptibly(int arg)
    throws InterruptedException {
    // 节点入队
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        for (;;) {
            // 获取当前节点的前驱节点
            final Node p = node.predecessor();
            // 如果前驱节点为 head 节点,说明有获取锁的资格,尝试获取锁
            if (p == head) {
                int r = tryAcquireShared(arg);
                if (r >= 0) {	// 如果 count == 0 时,r == 1,进入 if 逻辑
                    // 设置当前节点为头结点,并且将节点状态设置为 Propagate
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
            }
            // 判断节点是否需要挂起,讲解独占锁的时候已经解释过
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw new InterruptedException();
        }
    } finally {	// 节点被取消,讲解独占锁的时候已经解释过
        if (failed)
            cancelAcquire(node);
    }
}

此时队列中的第一个节点被唤醒,并且 state 已经减为 0,会进入 if (r >= 0) 的逻辑。

setHeadAndPropagate 方法的逻辑是将节点设置为 head 节点,并且状态设置为 Propagate,表示传播,代码如下:

private void setHeadAndPropagate(Node node, int propagate) {
    Node h = head; // Record old head for check below
    // 设置当前节点为新头节点
    setHead(node);
    // 如果旧的头节点或新的头节点为空或者其等待状态小于0(表示状态为SIGNAL/PROPAGATE)
    if (propagate > 0 || h == null || h.waitStatus < 0 ||
        (h = head) == null || h.waitStatus < 0) {
        // 需要传播,获取下一个节点
        Node s = node.next;
        // 如果下一个节点为空,或者是需要获取共享的节点
        if (s == null || s.isShared())
            // 继续调用 doReleaseShared 方法唤醒下一个节点
            doReleaseShared();
    }
}

Semaphore

Semaphore 基础

Semaphore,信号量,它保存了一系列的许可(permits),每次调用 acquire() 都将消耗一个许可,每次调用 release() 都将归还一个许可。

信号量的一个最主要的作用就是,来控制那些需要限制并发访问量的资源。具体来讲,信号量会维护“许可证”的计数,而线程去访问共享资源前,必须先拿到许可证。线程可以从信号量中去“获取”一个许可证,一旦线程获取之后,信号量持有的许可证就转移过去了,所以信号量手中剩余的许可证要减一。

同理,线程也可以“释放”一个许可证,如果线程释放了许可证,这个许可证相当于被归还给信号量了,于是信号量中的许可证的可用数量加一。当信号量拥有的许可证数量减到 0 时,如果下个线程还想要获得许可证,那么这个线程就必须等待,直到之前得到许可证的线程释放,它才能获取。由于线程在没有获取到许可证之前不能进一步去访问被保护的共享资源,所以这就控制了资源的并发访问量,这就是整体思路。

具体的场景:

image-20200619122547865

在这个场景中,我们的服务是中间这个方块儿,左侧是请求,右侧是我们所依赖的那个慢服务。出于种种原因(比如计算量大、依赖的下游服务多等),右边的慢服务速度很慢,并且它可以承受的请求数量也很有限,一旦有太多的请求同时到达它这边,可能会导致它这个服务不可用,会压垮它。所以我们必须要保护它,不能让太多的线程同时去访问。那怎么才能做到这件事情呢?

在讲解怎么做到这个事情之前,我们先来看一看,在通常的场景下,我们用一个普通线程池能不能做到这件事情。

public class SemaphoreDemo1 {

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(50);
        for (int i = 0; i < 1000; i++) {
            executorService.submit(new Task());
        }
        executorService.shutdown();
    }

    static class Task implements Runnable{
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "调用慢服务");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

在这段代码中,有一个固定 50 个线程的线程池,然后给线程池提交 1000 个任务,并且每一个任务所执行的内容,就是去休眠 3 秒钟,来模拟调用这个慢服务的过程。我们启动这个程序,会发现打印出来的结果如下所示:

pool-1-thread-2调用了慢服务
pool-1-thread-4调用了慢服务
pool-1-thread-3调用了慢服务
pool-1-thread-1调用了慢服务
pool-1-thread-5调用了慢服务
pool-1-thread-6调用了慢服务
...
(包含了pool-1-thread-1到pool-1-thread-5050个线程)

它会从线程 1 一直到线程 50 都去调用这个慢服务,当然实际调用顺序每次都会不一样,但是这 50 个线程都会去几乎同时调用这个慢服务,在这种情况下,就会导致我们的慢服务崩溃。

所以,必须严格限制能够同时到达该服务的请求数。比如,我们想限制同时不超过 3 个请求来访问该服务,该怎么实现呢?并且这里有一点值得注意,我们的前提条件是,线程池中确实有 50 个线程,线程数肯定超过了 3 个,那么怎么进一步控制这么多的线程不同时访问慢服务呢?我们可以通过信号量来解决这个问题。

public class SemaphoreDemo2 {

    static Semaphore semaphore = new Semaphore(3);

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(50);
        for (int i = 0; i < 1000; i++) {
            executorService.submit(new Task());
        }
        executorService.shutdown();
    }

    static class Task implements Runnable {
        @Override
        public void run() {
            try {
                semaphore.acquire();
                System.out.println(Thread.currentThread().getName() + "拿到了许可证,花费3秒执行慢服务");
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("慢服务执行完毕," + Thread.currentThread().getName() + "释放了许可证");
            semaphore.release();
        }
    }

}

在这段代码中我们新建了一个数量为 3 的信号量,然后又有一个和之前一样的固定 50 线程的线程池,并且往里面放入 1000 个任务。每个任务在执行模拟慢服务之前,会先用信号量的 acquire 方法获取到信号量,然后再去执行这 2 秒钟的慢服务,最后利用 release() 方法来释放许可证。

代码执行结果如下:

pool-1-thread-1拿到了许可证,花费2秒执行慢服务
pool-1-thread-2拿到了许可证,花费2秒执行慢服务
pool-1-thread-3拿到了许可证,花费2秒执行慢服务
慢服务执行完毕,pool-1-thread-1释放了许可证
慢服务执行完毕,pool-1-thread-2释放了许可证
慢服务执行完毕,pool-1-thread-3释放了许可证
pool-1-thread-4拿到了许可证,花费2秒执行慢服务
pool-1-thread-5拿到了许可证,花费2秒执行慢服务
pool-1-thread-6拿到了许可证,花费2秒执行慢服务
慢服务执行完毕,pool-1-thread-4释放了许可证
慢服务执行完毕,pool-1-thread-5释放了许可证
慢服务执行完毕,pool-1-thread-6释放了许可证
...

它会先让线程 1、2、3 拿到许可证,然后分别去执行这 2 秒钟的慢服务,直到执行完毕则会释放许可证,后面的线程才能进一步拿到许可证来执行服务。当前面 3 个线程还没有执行完毕,也就是还没有释放许可证的时候,后面的线程其实已经来请求了,它们也会尝试调用 acquire 方法,只不过这个时候会被阻塞住。通过执行结果可以看出,同时最多只有 3 个线程可以访问我们的慢服务。

特殊用法:一次性获取或释放多个许可证

那就是它可以一次性释放或者获取多个许可证。

比如 semaphore.acquire(2),里面传入参数 2,这就叫一次性获取两个许可证。同时释放也是一样的,semaphore.release(3) 相当于一次性释放三个许可证。

为什么要这样做呢?我们列举一个使用场景。比如说第一个任务 A(Task A )会调用很耗资源的方法一 method1(),而任务 B 调用的是方法二 method 2,但这个方法不是特别消耗资源。在这种情况下,假设我们一共有 5 个许可证,只能允许同时有 1 个线程调用方法一,或者同时最多有 5 个线程调用方法二,但是方法一和方法二不能同时被调用。

所以,我们就要求 Task A 在执行之前要一次性获取到 5 个许可证才能执行,而 Task B 只需要获取一个许可证就可以执行了。这样就避免了任务 A 和 B 同时运行,同时又很好的兼顾了效率,不至于同时只允许一个线程访问方法二,那样的话也存在浪费资源的情况,所以这就相当于我们可以根据自己的需求合理地利用信号量的许可证来分配资源。

**特殊用途:**推测执行机制,主线程有一个计算任务分配给多个子线程执行,看看谁执行得快,如果有线程得到结果后,主线程的代码逻辑继续往下执行。

代码示例如下:

public class SemaphoreDemo2 {

    public static void main(String[] args) throws Exception {
        Semaphore semaphore = new Semaphore(0);
        for(int i = 0; i < 3; i++) {
            new Thread(() -> {
                try {
                    Thread.sleep((new Random().nextInt(10) + 1) * 1000);
                    System.out.println(Thread.currentThread() + "分配同一个计算任务给不同的机器......");
                    semaphore.release();
                    System.out.println(Thread.currentThread() +"执行完毕!");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        }
        semaphore.acquire(1);
        System.out.println("一台机器已经先执行成功了,此时就可以收拢计算结果了");
    }
    
}

输出结果:

Thread[Thread-0,5,main]分配同一个计算任务给不同的机器......
Thread[Thread-0,5,main]执行完毕!
一台机器已经先执行成功了,此时就可以收拢计算结果了
Thread[Thread-1,5,main]分配同一个计算任务给不同的机器......
Thread[Thread-1,5,main]执行完毕!
Thread[Thread-2,5,main]分配同一个计算任务给不同的机器......
Thread[Thread-2,5,main]执行完毕!

当 Thread-0 执行完毕后,main 线程就可以继续往下执行,不需要等待 Thread-1 和 Thread-2 的计算结果。

信号量还有几个注意点:

  • 获取和释放的许可证数量尽量保持一致,否则比如每次都获取 2 个但只释放 1 个甚至不释放,那么信号量中的许可证就慢慢被消耗完了,最后导致里面没有许可证了,那其他的线程就再也没办法访问了;
  • 在初始化的时候可以设置公平性,如果设置为 true 则会让它更公平,但如果设置为 false 则会让总的吞吐量更高。
  • 信号量是支持跨线程、跨线程池的,而且并不是哪个线程获得的许可证,就必须由这个线程去释放。事实上,对于获取和释放许可证的线程是没有要求的,比如线程 A 获取了然后由线程 B 释放,这完全是可以的,只要逻辑合理即可。

信号量能被 FixedThreadPool 替代吗?

这个问题相当于,信号量是可以限制同时访问的线程数,那为什么不直接用固定数量线程池去限制呢?这样不是更方便吗?比如说线程池里面有 3 个线程,那自然最多只有 3 个线程去访问了。

我们在实际业务中会遇到这样的情况:假如,在调用慢服务之前需要有个判断条件,比如只想在每天的零点附近去访问这个慢服务时受到最大线程数的限制(比如 3 个线程),而在除了每天零点附近的其他大部分时间,我们是希望让更多的线程去访问的。所以在这种情况下就应该把线程池的线程数量设置为 50 ,甚至更多,然后在执行之前加一个 if 判断,如果符合时间限制了(比如零点附近),再用信号量去额外限制,这样做是比较合理的。

再说一个例子,比如说在大型应用程序中会有不同类型的任务,它们也是通过不同的线程池来调用慢服务的。因为调用方不只是一处,可能是 Tomcat 服务器或者网关,我们就不应该限制,或者说也无法做到限制它们的线程池的大小。但可以做的是,在执行任务之前用信号量去限制一下同时访问的数量,因为我们的信号量具有跨线程、跨线程池的特性,所以即便这些请求来自于不同的线程池,我们也可以限制它们的访问。如果用 FixedThreadPool 去限制,那就做不到跨线程池限制了,这样的话会让功能大大削弱。

基于以上的理由,如果想要限制并发访问的线程数,用信号量是更合适的。

内部类和构造方法

内部类 Sync:

abstract static class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = 1192457210091910933L;
    Sync(int permits) {
        setState(permits);
    }
    final int getPermits() {
        return getState();
    }
    // 非公平模式尝试获取许可
    final int nonfairTryAcquireShared(int acquires) {
        for (;;) {
            // 可用的许可
            int available = getState();
            int remaining = available - acquires;
            // 如果剩余许可小于 0 直接返回;
            // 如果剩余许可大于等于 0,尝试获取许可(更新 state 的值)
            if (remaining < 0 ||
                compareAndSetState(available, remaining))
                return remaining;
        }
    }
    // 释放许可
    protected final boolean tryReleaseShared(int releases) {
        for (;;) {
            // 可用的许可
            int current = getState();
            int next = current + releases;
            if (next < current) // overflow
                throw new Error("Maximum permit count exceeded");
            // 释放许可成功,可用许可 = 本来可用的许可 + 释放的许可
            if (compareAndSetState(current, next))
                return true;
        }
    }
    // 减少许可
    final void reducePermits(int reductions) {
        for (;;) {
            int current = getState();
            int next = current - reductions;
            if (next > current) // underflow
                throw new Error("Permit count underflow");
            if (compareAndSetState(current, next))
                return;
        }
    }
    // 销毁许可
    final int drainPermits() {
        for (;;) {
            int current = getState();
            if (current == 0 || compareAndSetState(current, 0))
                return current;
        }
    }
}

内部类 NonfairSync:

static final class NonfairSync extends Sync {
    private static final long serialVersionUID = -2694183684443567898L;
    NonfairSync(int permits) {
        super(permits);
    }
    protected int tryAcquireShared(int acquires) {
        // 调用父类的 nonfairTryAcquireShared 方法
        return nonfairTryAcquireShared(acquires);
    }
}

内部类 FairSync:

static final class FairSync extends Sync {
    private static final long serialVersionUID = 2014338818796000944L;
    FairSync(int permits) {
        super(permits);
    }
    // 尝试获取许可
    protected int tryAcquireShared(int acquires) {
        for (;;) {
            // 公平模式需要检测是否前面有排队的,如果有排队的直接返回失败
            if (hasQueuedPredecessors())
                return -1;
            // 没有排队的再尝试更新 state 的值
            int available = getState();
            int remaining = available - acquires;
            if (remaining < 0 ||
                compareAndSetState(available, remaining))
                return remaining;
        }
    }
}

构造方法:

// 构造方法,创建时要传入许可次数,默认使用非公平模式
public Semaphore(int permits) {
    sync = new NonfairSync(permits);
}
// 构造方法,需要传入许可次数,及是否公平模式
public Semaphore(int permits, boolean fair) {
    sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}

以下的方法都是针对非公平模式来描述,公平模式和非公平模式区别在于要判断队列中是否已经有人排队。

acquire()方法

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

调用 AQS 的 acquireSharedInterruptibly 方法,代码如下:

public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    if (tryAcquireShared(arg) < 0)
        // 如果获取许可失败
        // 调用 AQS 的 doAcquireSharedInterruptibly 方法进去阻塞队列中等待
        // 具体逻辑参考 CountDownLatch 中的讲解
        doAcquireSharedInterruptibly(arg);
}

尝试获取锁,tryAcquireShared 逻辑由子类实现,Semaphore 中内部类 Sync(非公平模式)的 tryAcquireShared 方法代码逻辑如下:

protected int tryAcquireShared(int acquires) {
    return nonfairTryAcquireShared(acquires);
}
    // 非公平模式尝试获取许可
    final int nonfairTryAcquireShared(int acquires) {
        for (;;) {
            // 可用的许可
            int available = getState();
            int remaining = available - acquires;
            // 如果剩余许可小于 0 直接返回;
            // 如果剩余许可大于等于 0,尝试获取许可(更新 state 的值)
            if (remaining < 0 ||
                compareAndSetState(available, remaining))
                return remaining;
        }
    }

如果有可用的许可,尝试获取许可,如果获取成功,返回剩余许可数量。如果可用许可不够你去获取,直接返回剩余许可数量(< 0),不会去尝试获取许可。

release() 方法

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

调用 AQS 的 releaseShared 方法,代码如下:

public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        // 释放许可成功,唤醒阻塞队列中节点
        doReleaseShared();
        return true;
    }
    return false;
}

tryReleaseShared 由子类实现,Semapore 中的实现逻辑如下:

    // 释放许可
    protected final boolean tryReleaseShared(int releases) {
        for (;;) {
            // 可用的许可
            int current = getState();
            int next = current + releases;
            if (next < current) // overflow
                throw new Error("Maximum permit count exceeded");
            // 释放许可成功,可用许可 = 本来可用的许可 + 释放的许可
            if (compareAndSetState(current, next))
                return true;
        }
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值