Semaphore
一个计数信号量。从概念上讲,信号量维护了一个许可集。如有必要,在许可可用前会阻塞每一个 acquire(),然后再获取该许可。每个 release() 添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不使用实际的许可对象,Semaphore 只对可用许可的号码进行计数,并采取相应的行动。
其实Semaphore就是维护了一个共享锁,通过state来决定同时可以多少个线程获取共享锁。
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
acquire就是获取共享锁。
acquireSharedInterruptibly
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted()) //线程被中断抛出中断异常
throw new InterruptedException();
if (tryAcquireShared(arg) < 0) //尝试获取锁
doAcquireSharedInterruptibly(arg);//调用方法doAcquireSharedInterruptibly
}
doAcquireSharedInterruptibly
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.SHARED); 创建”当前线程“的Node节点,且Node中记录的锁是”共享锁“类型;并将该节点添加到CLH队列末尾。
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; // help GC
failed = false;
return;
}
}
// 当前线程一直等待,直到获取到共享锁。
// 如果线程在等待过程中被中断过,则再次中断该线程(还原之前的中断状态)。
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
因此Semaphore就是同时可以让多个线程去获取共享锁,如果线程达到指定阀值,那么就等待,一直等到其它线程调用了release释放。
所以,Semaphore经常用来控制公共资源的并发访问。比如对某一个文件限制只能同时10个线程进行读取。
public class SemaphoreTest {
static Semaphore semaphore = new Semaphore(10);
public static void main(String[] args) {
for(int i = 0 ; i < 100; i++) {
new ReadFile().start();
}
}
static class ReadFile extends Thread{
@Override
public void run() {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "开始读取文件");
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
// System.out.println(Thread.currentThread().getName() + "释放了一个许可");
//semaphore.release();
}
}
}
}
某次运行结果:
Thread-0开始读取文件
Thread-1开始读取文件
Thread-4开始读取文件
Thread-2开始读取文件
Thread-3开始读取文件
Thread-5开始读取文件
Thread-7开始读取文件
Thread-9开始读取文件
Thread-6开始读取文件
Thread-8开始读取文件
结果是只有是个线程开始读取,其它线程都在等待,没有打印出来。如果把上面的注释代码去掉。那么有释放锁,就会让其它线程可以读取。