Semaphore源码解析(基于JDK8)
1 介绍
Semaphore 称为信号量,也是一个共享锁。通过一个内部类 Sync 继承 AQS,并重写了 tryAcquireShared
和 tryReleaseShared
。共享锁具体原理可以见我 AQS 的第二篇。AQS(二)共享锁
必须给初始的资源个数 state。每次 acquire 都会减少 state,如果为 0,会堵塞;release 增加 state。
就好比一个厕所管理员,站在门口,只有厕所有空位,就开门允许与空侧数量等量的人进入厕所。多个人进入厕所后,相当于N个人来分配使用N个空位。为避免多个人来同时竞争同一个侧卫,在内部仍然使用锁来控制资源的同步访问。
简单使用如下。
Semaphore semaphore = new Semaphore(10,true);//初始有10个资源,true 表示公平锁
semaphore.acquire();//获取,暂时获取不到会堵塞
semaphore.release();//释放
如果资源只有一个,类似于 ReentrantLock,但和 ReentrantLock 也有区别。具体在于 ReentrantLock 总是可重入的,可以重入若干次;而 Semaphore 并不区分是当前线程重入获取资源还是其他线程获取资源,所以重入也会减少 state,state 为 1 的话,说明不可重入。
在实现 AQS 的方法方面,共享锁实现需要自旋,而独占锁不需要。
2 tryAcquireShared
对于公平锁和非公平锁该方法的实现是不同的,公平锁会先看一下队列中是否有等待的线程,是的话则失败,否则不断自旋,只要资源够,就能成功;非公平锁每次不会看前面是否有线程,而是直接抢。
// FairSync
protected int tryAcquireShared(int acquires) {
for (;;) {
//公平锁在队列中不能有前面的节点,否则失败
if (hasQueuedPredecessors())
return -1;
int available = getState();
int remaining = available - acquires;
// 有两种情况会返回,一种是资源不够,另一种是 CAS 成功了。
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
// NonfairSync
protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
}
// Sync
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
// 有两种情况会返回,一种是资源不够,另一种是 CAS 成功了。
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
3 tryReleaseShared
在不溢出的情况下,不断自旋执行 CAS,直到某次成功后返回 true。
protected final boolean tryReleaseShared(int releases) {
for (;;) {
int current = getState();
int next = current + releases;
// 如果 releases 总是正的,出现下面的情况只可能是溢出
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next))
return true;
}
}
4 构造器和其他
// Semaphore
// 默认非公平锁
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
// 第二个参数表示是否是公平锁。
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
// Sync
// 自旋清空资源,并返回清空前的值
final int drainPermits() {
for (;;) {
int current = getState();
if (current == 0 || compareAndSetState(current, 0))
return current;
}
}
// 和 tryAcquireShared 的区别是 next 可以为负数
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;
}
}
5 使用
5.1 简单使用
下面这个例子中,资源只有两个,有10个线程,每个线程获取后会暂停5s然后释放,查看结果。
import java.util.concurrent.Semaphore;
public class B {
public static Semaphore semaphore = null;
public static void main(String[] args) throws Exception{
semaphore = new Semaphore(2,true);
Thread[] threads = new Thread[10];
for(int i=0;i<threads.length;i++){
threads[i] = new MyThread(semaphore,"线程"+i);
threads[i].start();
}
}
}
class MyThread extends Thread{
private Semaphore semaphore = null;
public MyThread(Semaphore semaphore,String name){
super(name);
this.semaphore = semaphore;
}
@Override
public void run() {
try {
semaphore.acquire();
System.out.println(getName()+"已获取资源");
Thread.sleep(5000);
} catch(Exception e){
e.printStackTrace();
}finally {
semaphore.release();
System.out.println(getName()+"已释放资源");
}
}
}
5.2 尝试可重入
由于重入也会使用资源,所以在只有一个资源的情况下,重入是不可能的。
import java.util.concurrent.Semaphore;
public class C {
public static Semaphore semaphore = null;
public static void main(String[] args) throws Exception{
semaphore = new Semaphore(1,true);
Thread thread = new MyThread2(semaphore);
thread.start();
}
}
class MyThread2 extends Thread{
private Semaphore semaphore = null;
public MyThread2(Semaphore semaphore){
this.semaphore = semaphore;
}
@Override
public void run() {
try {
semaphore.acquire();
enter();
} catch(Exception e){
e.printStackTrace();
}finally {
semaphore.release();
}
}
public void enter() {
try {
// 会被堵塞在这一步,无法继续
semaphore.acquire();
System.out.println("可以重入");
} catch(Exception e){
e.printStackTrace();
}finally {
semaphore.release();
}
}
}
5.3 直接释放
对于 Semaphore 来说,可以不执行 acquire 直接执行 release,就如下面的 testSemaphore,结果是 3.
对于 ReentrantLock,这显然不可能,必须先持有锁,才能释放。如果直接释放,testReentrantLock 会抛出 java.lang.IllegalMonitorStateException。
import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.ReentrantLock;
public class C {
public static void main(String[] args) throws Exception{
testReentrantLock();
//testSemaphore();
}
//没有异常,结果是3
public static void testSemaphore(){
Semaphore semaphore = new Semaphore(1,true);
semaphore.release();
semaphore.release();
System.out.println(semaphore.availablePermits());
}
//会抛出 java.lang.IllegalMonitorStateException
public static void testReentrantLock(){
ReentrantLock rt = new ReentrantLock();
rt.unlock();
}
}