同步工具类
闭锁
闭锁可以延迟线程的进度直到达到其终止状态,闭锁的作用相当于一扇门:在闭锁到达结束状态之前,这扇门一直是关闭的,并且没有任何线程能通过,当达到结束状态时,这扇门会打开并允许所有的线程通过
闭锁的作用:
1. 确保某个计算在其需要的所有资源都被初始化之后才执行
2. 确保某个服务在其依赖的所有其他服务都已经启动之后才启动
3. 等待直到某个操作的所有参与者就绪后再继续执行
1.CountDownLatch
使用
//计数10 的闭锁
CountDownLatch countDownLatch = new CountDownLatch(10);
//线程在闭锁上阻塞
countDownLatch.await();
//计数-1,当计数-10时,闭锁打开,唤醒闭锁上阻塞的所有线程
countDownLatch.countDown();
实现原理
基于AQS实现
//等待
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
//见AQS实现
doAcquireSharedInterruptibly(arg);
}
//节点状态为0返回1,其他返回-1
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
//计数减1
public void countDown() {
sync.releaseShared(1);
}
//AQS实现
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
//顺序释放阻塞在锁上的所有线程
doReleaseShared();
return true;
}
return false;
}
//Countdown实现尝试释放共享锁
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
//节点状态为0返回true
return nextc == 0;
}
}
闭锁打开后,所有阻塞线程按照执行await的先后顺序释放
示例:
/*
* 统计线程运行的时间
*/
public long timeTasks(int nThreads,final Runnable task) throws InterruptedException
{
final CountDownLatch startGate = new CountDownLatch(1);
final CountDownLatch endGate = new CountDownLatch(nThreads);
for (int i = 0; i < nThreads; i++)
{
new Thread(
() -> {
try {
startGate.await();
try
{
task.run();
}
finally {
endGate.countDown();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
).start();
}
long start = System.nanoTime();
startGate.countDown();
endGate.await();
long end = System.nanoTime();
return end - start;
}
2.FutureTask
FutureTask是闭锁的令一种形式,其get方法将会返回task的处理结果,如果task未被处理完成,get操作将会阻塞,直到线程返回或者线程中断
实现原理
FutureTask内部包含一个状态变量state,以及一个节点链表。进行get操作时如果state!=success,其会把当前线程绑定到节点,并把当前线程park掉,后续线程执行get操作时会被同样操作并链接到链表尾部。当task执行完成后,会顺序唤醒所有此链表上park掉的线程
示例:
FutureTask<String> task = new FutureTask(new Callable() {
@Override
public String call() throws Exception {
//doSomeThing
}
});
//启动线程开始执行task
new Thread(task).start();
//启动5个线程在task完成后doSomething
for(int i = 0; i<5 ; i++)
{
new Thread(()->{
try {
//将会阻塞到task完成
String result = task.get();
//doSomething
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
信号量
计数信号量用来控制同时访问某个特定资源的操作数量,或者同时执行某个指定操作的数量,计数信号量还可以用来实现某种资源池(C3p0数据库连接池),或者对容器施加边界
1.Semaphore
Semaphore中管理者一组虚拟的许可(permit),许可的初始数量可以通过构造函数来指定,在执行操作时可以首先获得许可(只要还有剩余的许可),并在使用以后释放许可,如果没有许可,那么acquire将阻塞直到有许可(或者直到被中断或者操作
超时),release方法将会返回一个许可给信号量(无关哪个线程释放许可都+1)
实现原理
基于AQS实现
1.acquire实现
/**
*获取信号量
*/
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
//AQS方法,参加AQS
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
//尝试获取请求失败,线程进入等待
doAcquireSharedInterruptibly(arg);
}
//子类实现,公平锁的tryAcquireShared
protected int tryAcquireShared(int acquires) {
for (;;) {
//已存在等待获取请求
if (hasQueuedPredecessors())
return -1;
int available = getState();
int remaining = available - acquires;
//获取请求成功 ,许可-1
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
2.release实现
public void release() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
//释放锁成功,唤醒阻塞在队列上的线程,公平情况下顺序唤醒,非公平情况下刚好有线程请求许可的话则被先竞争到,没用执行公平情况下的顺序唤醒
protected final boolean tryReleaseShared(int releases) {
for (;;) {
int current = getState();
int next = current + releases;
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next))
return true;
}
}
示例:Semaphore构建有界容器
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.Semaphore;
public class BoundedHashSet<T> {
private final Set<T> set;
private final Semaphore sem;
public BoundedHashSet(int bound) {
this.set = Collections.synchronizedSet(new HashSet<T>());
this.sem = new Semaphore(bound);
}
public boolean add(T o) throws InterruptedException {
sem.acquire();
boolean wasAdded = false;
try {
wasAdded = set.add(o);
return wasAdded;
} finally {
if (!wasAdded) {
sem.release();
}
}
}
public boolean remove(Object o)
{
boolean wasRemove = set.remove(o);
if(wasRemove)
{
sem.release();
}
return wasRemove;
}
}
栅栏
栅栏(Barrier)类似于闭锁,它能阻塞一组线程直到某个事件发生,栅栏与闭锁的关键区别在于,所有线程必须同时到达栅栏位置,才能继续执行。
闭锁用于等待事件,而栅栏用于等待其他线程。栅栏用于实现一些协议,例如几个家庭决定在某个地方集合,然后在讨论下一步要做的事情
1.CyclicBarrier
CyclicBarrier可以使一定数量的参与方反复的在栅栏位置汇集,它在并行迭代算法中非常有用:这种算法通常将一个问题分成一系列的相互独立的子问题,当线程到达栅栏位置时将调用await方法,这个方法将阻塞直到所有的线程都到达栅栏位置。如果所有线程都到达了栅栏位置,那么栅栏将打开,此时所有的线程都被释放,而栅栏将被重置以便下次使用,如果对await的调用超时,或者await阻塞的线程被中断,那么栅栏就被认为被打破,所有阻塞的await调用都将终止并抛出BrokenBarrierException.
如果成功的通过栅栏,那么await将会为每个线程返回一个唯一的到达索引号,我们可以利用这些索引来选取产生一个领导线程,并在下一次迭代中由该领导线程执行一些特殊的工作。
CyclicBarrier还可以使你将一个栅栏操作传递给构造函数,这是一个Runnable,当成功通过栅栏时会(在一个子线程中)执行它,但在阻塞线程被释放之前是不能执行的
实现原理
CyclicBarrier里有个计数统计count,每有一个线程进行await,count-1,线程阻塞在reentranLock.conndition上。当count=0时,如果runnable!=null,最后一个线程执行runable,然后执行conndition.notifyAll,唤醒所有的阻塞线程并重置count值
2.Exchanger
实现比较复杂,分析后续补充
功能
Exchanger,从名字上理解就是交换。Exchanger用于在两个线程之间进行数据交换,注意也只能在两个线程之间进行数据交换。线程会阻塞在Exchanger的exchange方法上,直到另外一个线程也到了同一个Exchanger的exchange方法时,二者进行数据交换,然后两个线程继续执行自身相关的代码。