Semaphore意为信号量,用法和CountDownLatch类似,也可以用来控制线程之间的协作关系,但通常用来控制同时访问的线程的数量。
先看看示例:
public class SemaphoreTest {
public static void main(String[] args) {
// 线程池
ExecutorService exec = Executors.newCachedThreadPool();
// 只能5个线程同时访问
final Semaphore semp = new Semaphore(5);
// 模拟20个客户端访问
for (int index = 0; index < 20; index++) {
final int NO = index;
Runnable run = new Runnable() {
public void run() {
try {
// 获取许可
semp.acquire();
System.out.println("Accessing: " + NO);
Thread.sleep((long) (Math.random() * 10000));
// 访问完后,释放 ,如果屏蔽下面的语句,则在控制台只能打印5条记录,之后线程一直阻塞
semp.release();
} catch (InterruptedException e) {
}
}
};
exec.execute(run);
}
// 退出线程池
exec.shutdown();
}
}
可以看到,在执行run方法里面的时候,需要先获取许可,只有获取之后才能接着执行,执行完之后再释放许可供后面的线程获取。
其实,Semaphore也是继承的AQS,然后实现其中的共享模式(和CountDownLatch一样,因为他们都支持多个线程获取资源,而不像reentrankLock,同时只能有一个线程。)
那么semp.acquire()方法是会阻塞线程的。而release()方法则会唤醒后继节点。
再给出根据http://blog.csdn.net/FoolishAndStupid/article/details/75676027 中得到的简单的结论(只是简单的结论,实际上要复杂一些):
我们推测:semp.acquire()方法是调用的AQS的acquireShared()方法,然后调用自定义同步器中实现的tryAcquireShared()方法,然后返回许可数量。当许可数量=0时表示没有资源了,则tryAcquireShared()返回<0,从而阻塞调用semp.acquire()的线程。
再看semp.release()方法:推测它是调用的AQS的releaseShared()方法,然后调用自定义同步器中的实现的tryReleaseShared()方法,当资源数>0时,返回true,从而唤醒调用acquire()而阻塞的线程。
给出推论之后再来看看源码:
semp.acquire()方法:
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
再来看看AQS中的acquireSharedInterruptibly()方法:
public final void acquireSharedInterruptibly(int arg) throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
其实就是调用的自定义同步器中实现的tryAcquireShared()方法。可以看到,semaphore中有一个公平的获取资源和非公平的获取资源2种:
公平的:
protected int tryAcquireShared(int acquires) {
Thread current = Thread.currentThread();
for (;;) {
Thread first = getFirstQueuedThread();
if (first != null && first != current)
return -1;
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
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;
}
}
公平和非公平的获取资源的区别在于:非公平的方式会直接用CAS的方式去获取资源,然后返回剩余的资源数。而公平的方式则是先从等待队列中取出第一个线程,然后判断是否为当前线程,如果不是,就直接返回-1,然后将它加入到等待队列中末尾。公平的方式其实就是根据队列中等待的先后顺序来分配资源。
还是符合我们图片中描述的。
再来看看release()方法:
public void release() {
sync.releaseShared(1);
}
AQS中的releaseShared():
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
就是自定义同步器中实现的tryReleaseShared()方法:
不过这个并没有公平和非公平之分。因为释放资源都是将自身线程中的资源释放。和等待队列没关系。
protected final boolean tryReleaseShared(int releases) {
for (;;) {
int p = getState();
if (compareAndSetState(p, p + releases))
return true;
}
}
可以看到就使用for循环+CAS的方式,直到设置成功,则返回true,然后就可以唤醒后继节点。
和图片中描述的相同
所以只要搞清楚自定义同步器需要实现的接口和AQS中的关系,AQS为我们做了哪些步骤,那对于理解j.u.c中各个同步类具有很大的帮助,我们也能用AQS来实现自己需要的同步器。