Java并发编程(九)Semaphore

64 阅读2分钟

1. Semaphore应用

一般用于流控。比如有一个公共资源,多线程都可以访问时,可以用信号量做限制。

连接池,内部的链接对象有限,每当有一个线程获取连接对象时,对信号量-1,当这个线程归还资源时对信号量+1。 如果线程拿资源时,发现Semaphore内部的资源个数为0,就会被阻塞。

public static void main(String[] args) throws InterruptedException, BrokenBarrierException {
    // 声明信号量
    Semaphore semaphore = new Semaphore(1);
    // 能否去拿资源
    semaphore.acquire();
    // 拿资源处理业务
    System.out.println("main");
    // 归还资源
    semaphore.release();
}

2. Semaphore核心源码分析

2.1 有参构造

Semaphore有公平和非公平两种竞争资源的方式。通过参数fair控制

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

// 设置资源个数,State其实就是信号量的资源个数
Sync(int permits) {
    setState(permits);
}

2.2 acquire

// acquireSharedInterruptibly
public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    if (tryAcquireShared(arg) < 0)
        doAcquireSharedInterruptibly(arg);
}

// 公平
protected int tryAcquireShared(int acquires) {
    for (;;) {
        // 公平方式,先好看队列中有没有排队的,有排队的返回-1,执行doAcquireSharedInterruptibly去排队
        if (hasQueuedPredecessors())
            return -1;
        // 那state
        int available = getState();
        // remaining = 资源数 - 1
        int remaining = available - acquires;
        // 如果资源不够,直接返回-1
        if (remaining < 0 ||
            // 如果资源够,执行CAS,修改state
            compareAndSetState(available, remaining))
            return remaining;
    }
}

// 非公平
final int nonfairTryAcquireShared(int acquires) {
    for (;;) {
        int available = getState();
        int remaining = available - acquires;
        if (remaining < 0 ||
            compareAndSetState(available, remaining))
            return remaining;
    }
}

2.3 release

// 公平和非公平共用
public void release() {
    sync.releaseShared(1);
}

public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        // 唤醒在AQS中排队的Node,去竞争资源
        doReleaseShared();
        return true;
    }
    return false;
}

// 信号量实现的归还资源
protected final boolean tryReleaseShared(int releases) {
    for (;;) {
        // 拿state
        int current = getState();
        // state + 1
        int next = current + releases;
        // 资源最大值,再+1,变为负数
        if (next < current)
            throw new Error("Maximum permit count exceeded");
        // CAS 改一手
        if (compareAndSetState(current, next))
            return true;
    }
}

2.4 AQS中PROPAGATE状态

JDK1.5中,使用信号量时,可能会造成在有资源的情况下,后继节点无法被唤醒。

image.png

在JDK1.8中,问题被修复,修复方式就是追加了PROPAGATE节点状态来解决。

共享锁在释放资源后,如果头节点为0,无法确认真的没有后继节点。如果头节点为0,需要将头节点的状态修改为-3,当最新拿到锁资源的线程,查看是否有后继节点并且为共享锁,就唤醒排队的线程