Semaphore源码分析

158 阅读3分钟

概述

Semaphore是信号量的意思,可以理解为高速路口的收费站,每次只允许最多N个车辆通过,每个车辆通过以后,后续车辆可以通过。可以对有限的公共资源设置并发量。比如有上万个文件进行文件处理,并且处理后要进行一些并要的入库。可以开辟几十个线程进行IO处理,但因为数据库连接池资源有限,就可以设置信号量,每次最多十个进行数据库连接,否则可能报错,无法连接数据库。

例子

public static void main(String[] args) {
    final int N = 30;
    final int SEMAPHORE_COUNT = 10;
    Semaphore semaphore = new Semaphore(SEMAPHORE_COUNT);

    for (int i = 0; i < N; ++i)
        new Thread(new Worker(semaphore)).start();
    System.out.println(currentThread().getName() + ": end");
}

static class Worker implements Runnable {
    private final Semaphore semaphore;

    public Worker(Semaphore semaphore) {
        this.semaphore = semaphore;
    }

    @Override
    public void run() {
        try {
            doWork();
            semaphore.acquire();
            System.out.println(currentThread().getName() + ": save db");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            semaphore.release();
        }

    }

    private void doWork() {
        System.out.println(currentThread().getName() + ": is working");
    }
}

运行结果:

Thread-0: is working
Thread-3: is working
Thread-3: save db
Thread-2: is working
Thread-1: is working
Thread-1: save db
。。。

上面的程序开辟30个线程进行数据处理,处理结束,入库前先进行semaphore.acquire操作,成功返回后才进行入库操作。

比较Semaphore和ReentrantLock

  1. Semaphore是共享锁,ReentrantLock是独占锁
  2. Semaphore和ReentrantLock代码结构很像,都可以实现公平锁和非公平锁
  3. 默认都是实现非公平锁

acquire方法

//Semaphore构造方法必须传入许可量
public Semaphore(int permits) {
    sync = new NonfairSync(permits);
}
//acquire方法调用AQS的acquireSharedInterruptibly
//根据AQS源码的分析,会调用子类的tryAcquireShared尝试加锁
public void acquire() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}
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);
    }
}
//如果剩余量大于等于0,CAS操作更新,更新成功返回请求成功
//否则remaining<0,返回小于0的值,请求失败
final int nonfairTryAcquireShared(int acquires) {
    for (;;) {
        int available = getState();
        int remaining = available - acquires;
        if (remaining < 0 ||
            compareAndSetState(available, remaining))
            return remaining;
    }
}
  1. acquire方法调用AQS的acquireSharedInterruptibly方法,acquireSharedInterruptibly方法内部会调用子类的tryAcquireShared方法进行尝试加锁,加锁失败则进去等待队列
  2. Semaphore默认使用NonfiarSync(不公平的同步器),我们看到最终调用nonfairTryAcquireShared这个方法,该方法是自旋CAS操作,保证方法的正确性
  3. 当前state值减去acquires的量大于等于0时,进行CAS操作,成功则返回加锁成功
  4. 不成功,说明被别的线程更改了state的值,进行下一次计算
  5. 如果当前state值减去acquires的量小于0直接返回失败

release方法

public void release() {
    sync.releaseShared(1);
}
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;
    }
}

我们知道releaseShared方法在AQS类中会调用子类的tryReleaseShared方法
可以看到tryReleaseShared方法很简单就是就行一个相加操作,如果相加后超过了int的最大值抛出异常,否则CAS更新,更新成功后返回成功释放

再看公平信号量的实现FairSync.tryAcquireShared

protected int tryAcquireShared(int acquires) {
    for (;;) {
        if (hasQueuedPredecessors())
            return -1;
        int available = getState();
        int remaining = available - acquires;
        if (remaining < 0 ||
            compareAndSetState(available, remaining))
            return remaining;
    }
}

可以看到和非公平的tryAcquireShared相比,在进行state与acquires相减前,进行hasQueuedPredecessors的判断,判断队列中是否有当前节点的前辈

public final boolean hasQueuedPredecessors() {
    Node t = tail; // Read fields in reverse initialization order
    Node h = head;
    Node s;
    return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());
}

如果队列不为空,而且第二节点不是当前线程,队列就存在当前节点的前辈(AQS中就分析过,每次都唤醒队列的第二个节点)