《Java源码分析》:Semaphore
Semaphore 是一个计数信号量。从概念上讲,信号量维护了一个许可集。如有必要,在许可可用前会阻塞每一个 acquire(),然后再获取该许可。每个 release() 添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不使用实际的许可对象,Semaphore 只对可用许可的号码进行计数,并采取相应的行动。
说白了,Semaphore是一个计数器,在计数器不为0的时候对线程就放行,一旦达到0,那么所有请求资源的新线程都会被阻塞,包括增加请求到许可的线程,也就是说Semaphore不是可重入的。每一次请求一个许可都会导致计数器减少1,同样每次释放一个许可都会导致计数器增加1,一旦达到了0,新的许可请求线程将被挂起。
缓存池整好使用此思想来实现的,比如链接池、对象池等。下面看一个具体的实现。
public class ObjectCache<T> {
interface ObjectFactory<T>{
T makeObject();
}
private Semaphore semaphore;
private int capacity;
private ObjectFactory<T> factory;
private Lock lock;
private Node head,tail;
private class Node{
T obj;
Node next;
}
public ObjectCache(int capacity,
ObjectFactory<T> factory) {
this.capacity = capacity;
this.factory = factory;
this.lock = new ReentrantLock();
this.semaphore =new Semaphore(capacity);
head = tail = null;
}
public T getObject(){
try {
semaphore.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
return getNextObject();
}
private T getNextObject() {
lock.lock();
try{
if(head==null){
return factory.makeObject();
}
T value = head.obj;
Node next = head.next;
if(next==null){
tail = null;
}
else{
head.next = null;
head = next;
}
return value;
}finally{
lock.unlock();
}
}
public void returnObject(T t){
returnObjectToPool(t);
semaphore.release();
}
public void returnObjectToPool(T t){
lock.lock();
try{
Node node = new Node();
node.obj = t;
if(head==null){
head = tail = node;
}
else{
tail.next = node;
tail = node;
}
}finally{
lock.unlock();
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
Semaphore的构造函数
当我们使用Semaphore semaphore = new Semaphore(10)
时其内部的实例化如下:
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
NonfairSync(int permits) {
super(permits);
}
Sync(int permits) {
setState(permits);
}
protected final void setState(int newState) {
state = newState;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
Semaphore直接new了一个NonfairSync类的对象。这里要说明下,Semaphore类是委托给实现了AQS类的Sync类的两个子类FairSync、NonFairSync来实现的。
Semaphore信号量和ReentrantLock锁一样,也存在公平和不公平。
如果Semaphore委托给FairSync类实现,就是公平信号量。
如果Semaphore委托给NonFairSync类实现,就是非公平信号量。
那么什么是公平Semaphore呢??按照FIFO队列中的顺序分配Semaphore所管理的许可就是公平的。
非公平Semaphore信号量对于任何申请许可的线程来说,都是第一时间看是否有多余的许可,如果有则给此线程,如果没有则进队列排队等待,而不是此线程直接进AQS队列排队等待按顺序来拿到许可,利用此间隙来分配许可可以提高并发量。但是会引发一个问题:越活跃的线程越能够拿到许可,造成“饥渴死”现象。
下面我们来看Semaphore的两个比较常用方法的内部实现。
void acquire()
根据API文档的介绍,我们知道
void acquire()
从此信号量获取一个许可,在提供一个许可前一直将线程阻塞,否则线程被中断。
下面我们来从源码角度看下这个方法的内部实现。
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
这个方法直接调用的是AQS的acquireSharedInterruptibly(int arg)方法。
这个方法具体详细的功能见翻译的注释,简单来说:获取一个许可,如果没有则阻塞。
既然此方法调用了AQS的acquireSharedInterruptibly(int arg)方法,那我们就业来看下。
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
代码中的中文是对源码中的注释翻译得到的,可能翻译的比较烂,凑合的看哈。
此函数做了如下几件事:
1、检测此线程是否被中断了,如果中断,则抛中断异常。如果没有被中断,则进行2
2、调用tryAcquireShares(int arg)方法以共享模式来获取对象(锁),如果此方法值大于等于0则表明获取到锁立即返回,否则进行 3
3、由于没有获取到锁,则调用doAcquireSharedInterruptibly方法进入AQS同步队列进行自旋等待。
下面就先看下tryAcquireShares(int arg)方法是怎么样的。
由于存在非公平和公平Semaphore,因此这里有一点点不同,也只有这里不同。
非公平锁的tryAcquireShared方法
protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
}
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
公平锁的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;
}
}
比较非公平的公平的源码可以看到,公平Semaphore对象对许可的分配是按照FIFO队列来分配的,以保证公平性。因此其首先调用了hasQueuedPredecessors方法来判断当前线程是否是AQS队列中的头结点,如果不是,则不给于分配需要加入到同步队列中等待。而非公平的Semaphore对象就不是这样的,有许可我们就分配出去,不需要排队等待。
当一个线程第一次获取共享锁失败之后,就会调用doAcquireSharedInterruptibly(int
arg)方法来自旋等待获取锁。
该方法的代码如下:
/**
* Acquires in shared interruptible mode.
* @param arg the acquire argument
*/
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
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;
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
该方法的具体思路如下:
1、首先调用addWaiter方法将当前线程构成的节点加入到同步队列AQS中
2、将一直自旋检测该线程节点的前驱节点是否为头结点,如果是则调用tryAcquireShared尝试的获取锁。如果不是,则进行 3
3、判断此节点是否需要阻塞以及异常检测。
acquire方法总结如下:
1、尝试在非公平模式下获取一个许可,或者叫做锁。如果获取到则立即返回并将许可计数器减一,如果没有获取到,则进行2
2、进入到AQS队列自旋等待,当此节点的前驱是头结点后,又开始尝试获取锁。直至成功获取或中断取消。
以上就是关于acquire()的内部实现过程。
release()方法:释放一个许可
当调用semaphore.release()方法的内部实现是怎样的呢??
下面我们来看下release方法的内部实现。
public void release() {
sync.releaseShared(1);
}
此方法直接调用了AQS的releaseShared(int arg)方法。
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
此函数总共干了两件事:
1、调用tryReleaseShared方法来完成AQS状态位的设置。如果设置成功,则进行2
2、调用doReleaseShared()来唤醒在AQS中等待的需要许可的后继节点来获取许可。
tryReleaseShared(int releases)方法的代码比较简单,就是CAS来设置了状态为state。
protected final boolean tryReleaseShared(int releases) {
for (;;) {
int current = getState();
int next = current + releases;
if (next < current)
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next))
return true;
}
}
doReleaseShared()方法的功能也比较清晰,唤醒AQS队列中需要许可的继任节点。具体代码不再分析。
/*
* Release action for shared mode -- signals successor and ensures
* propagation. (Note: For exclusive mode, release just amounts
* to calling unparkSuccessor of head if it needs signal.)
翻译:以共享模式释放,发信号给后继的一些节点。
注意:对于独占模式,只会调用unparkSuccessor来唤醒AQS队列总的头结点(如果其需要信号)
*/
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
以上就是关于release方法的内部实现,是不是思路还是比较简单的。应该这么说,我们看的ReentrantLock、Condition、CountDownLatch的思想实现都是如此,当我们看这些源码看的比较多的时候,发现这些类库的实现思想都是借助于AQS类来实现的。
Semaphore的应用:实现生产消费者模型
最后以一个生产消费者模型来结束Semaphore类的分析。
例子是用3个Semaphore对象来实现的。
public class SemaphoreDemo {
private Semaphore produceSem;
private Semaphore customerSem;
private Semaphore mutex;
private Object[] warehouse;
private int head,tail;
public SemaphoreDemo(int capacity){
produceSem = new Semaphore(capacity);
customerSem = new Semaphore(0);
warehouse = new Object[capacity];
head = 0;
tail = 0;
mutex = new Semaphore(1);
}
public void put(Object o){
try {
produceSem.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
putObject(o);
customerSem.release();
}
private void putObject(Object obj){
try {
mutex.acquire();
warehouse[tail++] = obj;
if(tail==warehouse.length){
tail = 0;
}
System.out.println(Thread.currentThread().getName()+"生产产品: "+(Integer)obj);
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
mutex.release();
}
}
public Object get(){
try {
customerSem.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
Object obj = getObject();
produceSem.release();
return obj;
}
private Object getObject() {
try {
mutex.acquire();
Object obj = warehouse[head];
head++;
if(head==warehouse.length){
head = 0;
}
System.out.println(Thread.currentThread().getName()+"拿到产品: "+obj);
return obj;
} catch (InterruptedException e) {
e.printStackTrace();
}
finally{
mutex.release();
}
return null;
}
private static AtomicInteger at = new AtomicInteger(0);
public static void main(String[] args){
SemaphoreDemo sd = new SemaphoreDemo(10);
for(int i=0;i<3;i++){
new Thread(new Runnable(){
@Override
public void run() {
while(true){
int val = at.incrementAndGet();
sd.put(val);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
},"produceThread"+i).start();
new Thread(new Runnable(){
@Override
public void run() {
while(true){
sd.get();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
},"customerThread"+i).start();
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121