1、闭锁(Latch)
2、信号量(Semaphore)
3、栅栏(Barrier)
1、闭锁(Latch):作用相当于一扇门:在闭锁到达结束之前,这扇门一直 是关闭的,并且没有任何线程能通过,当到达结束状态时,这扇门会打开并允许所有的线程通过。当闭锁到达结束状态后,将不会再改变状态,因此这扇门将保持永远打开状态(意思是指:一旦进入终止状态,就不能被重置,简单地说就是一次性的)。闭锁可以用来确保某些活动直到其它活动都完成后才继续执行。
eg:确保某个计算再起需要的所有资源都被初始化之后才继续执行。
确保某个读物再起依赖的所有其它服务都 已经启动后才启动。
等待直到某个操作的额所有参与者都就绪再继续执行(eg:多玩家游戏中的所有玩家)。
CountDownLatch就是一种闭锁实现,它可以使一个或者多个线程等待一组事件发生。如图
首先构造方法CountDownLatch(int count);这个count计数器(正数)表示需要等待的事件数量。
countDown()方法就是将count递减,表示有一个事件已经发生了。
await()方法就是等待count到达0,这表示所有需要等待的事件都已经发生。如果count计数器的值非零,那么await()会一直阻塞到计数器为零,或者等待中的线程中断,或者等待超时。
闭锁的两种常见用法:如TestHarness ,StartGate和EndGate两个闭锁,每个工作线程在气动门上等待,从而保证所以线程但都就绪后才开始执行。而每个工作线程最后做的一件事就是调用EndGate的countDown。从而保证主线程高效的等待所有工作线程都执行完成。启动门(startGate)将使得主线程能够同时释放所有工作线程,结束门(EndGate)能够等待最后一个线程执行完成,而不是顺序的等待每个线程执行完成。因此可以统计所消耗的时间
package simple;
import java.util.concurrent.CountDownLatch;
public class TestHarness {
public long timeTask(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){
Thread t = new Thread(){
public void run(){
try {
startGate.await();
try{
task.run();
}finally{
endGate.countDown();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
t.start();
}end for
long start = System.nanoTime();
startGate.countDown();
endGate.await();
long end = System.nanoTime();
return end -start;
}
public static void main(String []args){
TestHarness th = new TestHarness();
long time = 0;
try {
time = th.timeTask(10, new Thread(){
public void run(){
for(int i = 0;i<10000; ++i){}
}
});
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("历时"+time+"纳秒");
}
}
FutureTask也可以用作闭锁。一种可生成结果的计算(A cancellable asynchronous computation)。通过实现接口RunnableFuture,而接口RunnableFuture继承了Runnable, Future两个接口。其相关方法如图
cancel()方法尝试中断。当task还未开始,调用cancel后该task就不再运行了。但当task已启动后,cancel(boolean)传进的参数决定在尝试取消该task是否应该被中断( If the task has already started, then the mayInterruptIfRunning parameter determines whether the thread executing this task should be interrupted in an attempt to stop the task.)。
get()方法的行为取决于task的状态,如果task已经完成,则get立即返回结果,否则get将一直阻塞到task进入完成状态,然后返回结果或者抛出异常。
run()方法就是执行计算逻辑。task的状态转换就是由此变化的。如果run()方法未执行,则task状态处于还没执行。run()方法已经执行则task状态包含已完成状态,正在执行状态,取消结束状态,异常状态。
FutureTask 可以用来执行以下时间比较长的计算,
package simple;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class PreLoader {
private final FutureTask<Long> future = new FutureTask(new Callable<Long>(){
public Long call() throws InterruptedException{
Thread.sleep(10000);
return new Long(1234);
}
});
private final Thread th = new Thread(future);
public void start(){
th.start();
}
public Long load(){
new Thread(){
public void run(){
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
return new Long(123);
}
public Long get() throws InterruptedException{
System.out.println("start calculating.......");
long start = System.nanoTime();
Long oj = null;
try {
oj = future.get();
} catch (ExecutionException e) {
Throwable cause = e.getCause();
if(cause instanceof RuntimeException){
throw (RuntimeException)cause;
}else if(cause instanceof Error){
throw (Error)cause;
}
}
long end = System.nanoTime();
System.out.println("耗时:"+(end-start)+"纳秒得到"+oj);
return oj;
}
public static void main(String []args) throws InterruptedException{
PreLoader pl = new PreLoader();
pl.start();
long begin = System.nanoTime();
pl.get();
long end = System.nanoTime();
System.out.println("等待了"+(end-begin));
}
}
信号量(Semaphore):Semaphore中管理着一组虚拟的许可(permit),许可的初数量可通过构造函数来指定,在执行操作时可以首先获得许可(只要还要有剩余的许可,没有许可,那么将一直阻塞直到由许可),使用后再释放许可。用来控制同时访问某个特定资源的操作数量,或者同时执行某个指定操作的数量。还可以用来实现某种资源池,或者对容器加边界。
Semaphore(int,boolean)构造方法,int参数表示有多少个许可,boolean参数表示许可是否以先进先出(FIFO)的次序处理许可。默认是无序的。计算信号量的一种简化形式就是二值信号量。即初始值为1的信号量,可以作为互斥体。Semaphore可以用作数据库连接池。
acquire()方法表示获取一个许可,如果没有,则一直阻塞直到有许可。acquire(int)表示获取多个(int)的许可
release()方法表示释放一个许可。release(int) 表示释放多个(int)许可
package simple;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.Semaphore;
public class BoundedHashSet {
private final Set set;
private final Semaphore sem;
public BoundedHashSet(int bound){
this.set = Collections.synchronizedSet(new HashSet());
sem = new Semaphore(bound);
}
public boolean add(Object o) throws InterruptedException{
sem.acquire();
boolean wasAdded = false;
try{
wasAdded = set.add(o);
}finally{
sem.release();
}
return wasAdded;
}
public boolean remove(Object o) throws InterruptedException{
sem.acquire();
boolean wasRemoved = set.remove(o);
if(wasRemoved){
sem.release();
}
return wasRemoved;
}
public static void main(String []args) throws InterruptedException{
int n=0;///n=1,2分别有不同的输出,可以观察其中的变化
BoundedHashSet bs = new BoundedHashSet(n);
if(bs.remove("213")){
System.out.println("object has been removed");
}else{
System.out.println("waiting .........");
}
System.out.println("remove "+bs.set);
if(bs.add("213")){
System.out.println("add successful");
}else{
System.out.println("waiting for add ........");
}
System.out.println("add:"+bs.set);
if(bs.remove("213")){
System.out.println("object has been removed");
}else{
System.out.println("waiting .........");
}
System.out.println("remove after add :"+bs.set);
}
}
栅栏(Barrier):它能阻塞一组线程知道某个事件发生,与闭锁类似。栅栏与闭锁的区别在于,所有线程必须到达栅栏位置,才能继续执行。闭锁用于等待事件发生,栅栏用于等待其他线程。CyclicBarrier和Exchange
CyclicBarrier:可以使一组线程反复的在栅栏位置汇聚。一组线程在某个时间点达到一致。例如一些迭代算法,每一轮都依赖上一轮的结果(每一轮的所有计算都执行完毕才能进入下一轮步骤)。
eg:f(n) = a(n-1) + b(n-1); 其中a(n) 和b(n)耗时计算而且可以并行计算。就可以采用CyclicBarrier
CyclicBarrier的一些方法如图
package simple;
import java.util.concurrent.*;
/**
* CellularAutomata
*
* Coordinating computation in a cellular automaton with CyclicBarrier
*
* @author Brian Goetz and Tim Peierls
*/
public class CellularAutomata {
private final Board mainBoard;
private final CyclicBarrier barrier;
private final Worker[] workers;
public CellularAutomata(Board board) {
this.mainBoard = board;
int count = Runtime.getRuntime().availableProcessors();
this.barrier = new CyclicBarrier(count,
new Runnable() {
public void run() {
mainBoard.commitNewValues();
}});
this.workers = new Worker[count];
for (int i = 0; i < count; i++)
workers[i] = new Worker(mainBoard.getSubBoard(count, i));
}
private class Worker implements Runnable {
private final Board board;
public Worker(Board board) { this.board = board; }
public void run() {
while (!board.hasConverged()) {
for (int x = 0; x < board.getMaxX(); x++)
for (int y = 0; y < board.getMaxY(); y++)
board.setNewValue(x, y, computeValue(x, y));
try {
barrier.await();
} catch (InterruptedException ex) {
return;
} catch (BrokenBarrierException ex) {
return;
}
}
}
private int computeValue(int x, int y) {
// Compute the new value that goes in (x,y)
return 0;
}
}
public void start() {
for (int i = 0; i < workers.length; i++)
new Thread(workers[i]).start();
mainBoard.waitForConvergence();
}
interface Board {
int getMaxX();
int getMaxY();
int getValue(int x, int y);
int setNewValue(int x, int y, int value);
void commitNewValues();
boolean hasConverged();
void waitForConvergence();
Board getSubBoard(int numPartitions, int index);
}
}