概述
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
- Semaphore是共享锁,ReentrantLock是独占锁
- Semaphore和ReentrantLock代码结构很像,都可以实现公平锁和非公平锁
- 默认都是实现非公平锁
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;
}
}
- acquire方法调用AQS的acquireSharedInterruptibly方法,acquireSharedInterruptibly方法内部会调用子类的tryAcquireShared方法进行尝试加锁,加锁失败则进去等待队列
- Semaphore默认使用NonfiarSync(不公平的同步器),我们看到最终调用nonfairTryAcquireShared这个方法,该方法是自旋CAS操作,保证方法的正确性
- 当前state值减去acquires的量大于等于0时,进行CAS操作,成功则返回加锁成功
- 不成功,说明被别的线程更改了state的值,进行下一次计算
- 如果当前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中就分析过,每次都唤醒队列的第二个节点)