信号量,闭锁,栅栏都是java中锁的一个实现和ReentrantLock锁类似,只是有各自不同的特点。ReentrantLock是对唯一资源的访问进行并发的控制。
一:信号量(Semaphore)
信号量是对有限数量的资源访问进行并发控制,假若有n个资源,在某个时刻最多允许n个线程同时访问。信号量在Java中的具体实现是Semaphore.
源码核心部分:
// 给定许可证数量创建非公平信号量锁
public Semaphore(int permits) {
sync = new NonfairSync(permits);
// 给定许可证数量,以及是否非公平创建信号量锁
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
使用时:
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public void release() {
sync.releaseShared(1);
}
案例:
public class MianDemo {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(5);
ExecutorService exec = Executors.newCachedThreadPool();
for(int i=0; i<10; i++){
int finalI = i;
exec.submit(new Runnable() {
@Override
public void run() {
try {
semaphore.acquire();
System.out.println("i :" + finalI);
Thread.sleep(10000);
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
}
只有Semaphore有许可证才可以继续访问,否则等待…
二:闭锁(CountDownLatch)
允许一个或多个线程一直等待,直到其他线程的操作执行完后再执行。CountDownLatch是通过一个计数器来实现的,计数器的初始值为线程的数量。每当一个线程完成了自己的任务后,计数器的值就会减1。当计数器值到达0时,它表示所有的线程已经完成了任务,然后在闭锁上等待的线程就可以恢复执行任务。
源码跟踪:
// 等待
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
进AQS:
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
继续:
protected int tryAcquireShared(int arg) {
throw new UnsupportedOperationException();
}
CountDownLatch实现:
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
表示当state==0可获得执行
public void countDown() {
sync.releaseShared(1);
}
表示将计数器减去1
三:栅栏(CyclicBarrier)
用于阻塞一组线程直到某个事件发生。所有线程必须同时到达栅栏位置才能继续执行下一步操作,且能够被重置以达到重复利用。而闭锁是一次性对象,一旦进入终止状态,就不能被重置。
CyclicBarrier barrier = new CyclicBarrier(令牌数量);
barrier.await();//屏障点,消耗一个令牌,当令牌消耗到任务数量的时候,栅栏就会全部放开
barrier.reset();//重置,可以复用栅栏,不用重新创建栅栏对象
具体实现案例:
/**
* 所有线程达到屏障点后,开始执行的任务
*/
public class ReachAfterTask implements Runnable{
@Override
public void run() {
System.out.println("发钱.....");
}
}
主要线程任务:
public class ExamTask implements Runnable{
private CyclicBarrier cyclicBarrier;
public ExamTask(CyclicBarrier cyclicBarrier){
this.cyclicBarrier = cyclicBarrier;
}
@Override
public void run() {
System.out.println("到达集合点");
try {
cyclicBarrier.await();
System.out.println("同时执行此任务....");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}
任务执行:
public class ExamMain {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(10, new ReachAfterTask());
ExamTask examTask = new ExamTask(cyclicBarrier);
for(int i=0; i<10; i++){
new Thread(examTask).start();
}
}
}
执行结果:
到达集合点
到达集合点
到达集合点
到达集合点
到达集合点
到达集合点
到达集合点
到达集合点
到达集合点
到达集合点
发钱.....
同时执行此任务....
同时执行此任务....
同时执行此任务....
同时执行此任务....
同时执行此任务....
同时执行此任务....
同时执行此任务....
同时执行此任务....
同时执行此任务....
同时执行此任务....
通过执行结果发现到多个要求的屏障点达到后,会执行需要执行的任务,以及每个线程会执行后续的代码。