源码之Semaphore信号量

前言

Semaphore 用来限制能同时访问共享资源的线程上限。它的底层大量用到了 CAS 和 AQS。下面,笔者将带领大家阅读通过加锁解锁流程来阅读 Semaphore 的源码。此源码基于 JDK8 来查看,带大家深入浅出的看懂源码

加锁解锁流程

Semaphore 像一个停车场,permits 就好像停车位数量,当线程获得了 permits 就好比获得了一个停车位,然后停车场显示空余车位减一

刚开始,permits (state) 为 3,这时 5 个线程来获取资源

假设其中 Thread-1、Thread-2、Thread-4 CAS 竞争成功,而 Thread-0 和 Thread-3 竞争失败,进入 AQS 队列 park 阻塞

这时,如果 Thread-4 释放了 permits,则状态如下

接下来 Thread-0 竞争成功,permits 再次设置为 0,设置自己为 head 节点,断开原来的 head 节点,unpark 接下来的 Thread-3 节点,但由于 permits 是 0,因此 Thread-3 在尝试不成功后再次进入 park 状态

 

源码分析

获取信号量源码:

// 构造方法
public Semaphore(int permits) {
    sync = new NonfairSync(permits);  // 内部实际上是调用了同步器类,默认是非公平的同步器
}

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

    NonfairSync(int permits) {
        super(permits);  // 内部调用的父类的构造,实际就是把传递的 premits 赋值给了 AQS 中的 state
    }

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

// 获取许可
public void acquire() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);  // 获取许可数为 1
}

public final void acquireSharedInterruptibly(int arg) throws InterruptedException {
    if (Thread.interrupted()) {
        throw new InterruptedException();
    }
    // tryAcquireShared() 尝试获取读锁,由子类实现,返回负数表示获取失败
    // Semaphore 实现此方法代码在第 13 行
    if (tryAcquireShared(arg) < 0) {
        doAcquireSharedInterruptibly(arg);  
    }
}

final int nonfairTryAcquireShared(int acquires) {
    for (;;) {
        // 拿到状态值
        int available = getState();
        // 剩余许可数
        int remaining = available - acquires;
        if (remaining < 0 ||
            compareAndSetState(available, remaining)) // 通过 CAS 无锁尝试修改
            return remaining;
    
        // 当 remaining < 0 后,不会执行后续的 CAS 操作,直接返回负数
    }
}

// 如果许可已经没了,则后续线程会进入此方法
private void doAcquireSharedInterruptibly(int arg) throws InterruptedException {
    // 创建 Node 节点,节点类型设置为共享读
    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;
                }
            }
            // shouldParkAfterFailedAcquire() :内部判断等待状态,再一次 CAS 重试尝试获取许可
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())  // parkAndCheckInterrupt() 内部调用 park() 让线程阻塞等待
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
    if (ws == -1)
        return true;
    if (ws > 0) {
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        // 此时,等待状态为 SHARED,再次通过 CAS 操作重试
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

释放信号量源码:

public void release() {
    // 释放一个许可
    sync.releaseShared(1);
}

// 释放许可
public final boolean releaseShared(int arg) {
    // tryReleaseShared() 尝试释放锁
    if (tryReleaseShared(arg)) {
        // 成功释放许可后,进入下方方法
        doReleaseShared();
        return true;
    }
    return false;
}

protected final boolean tryReleaseShared(int releases) {
    for (;;) {
        // 获得 state。如果没有许可了,则此处为 0
        int current = getState();
        int next = current + releases;
        if (next < current) // overflow
            throw new Error("Maximum permit count exceeded");
        if (compareAndSetState(current, next)) {  // 使用 CAS 的方式将 0 变成 1
            return true;
        }
    }
}

private void doReleaseShared() {
    for (;;) {
        Node h = head;
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            if (ws == Node.SIGNAL) {  // 如果 waiteStatus 状态为 -1,表示有后继节点需要唤醒
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) {  // 尝试先把 -1 改成 0
                    continue;            // loop to recheck cases
                }
                // 唤醒后继节点  ->  跳转到加锁源码中的第 70 行
                unparkSuccessor(h);
            }
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        if (h == head)                   // loop if head changed
            break;
    }
}

// 线程从 park 阻塞状态被唤醒后,进入此方法
private void setHeadAndPropagate(Node node, int propagate) {
    Node h = head;
    setHead(node);  // 设置当前节点为头节点

    if (propagate > 0 || h == null || h.waitStatus < 0 ||
        (h = head) == null || h.waitStatus < 0) {
        Node s = node.next;
        if (s == null || s.isShared()) {
            // 如果下一个节点是共享节点,唤醒后继节点
            doReleaseShared();
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值