对于InterruptedException应该传递或者恢复中断,建议不要捕获他但是不做任何处理,这样做会丢失线程中断的数据,从而剥夺了上层栈的代码处理终端的机会。只有一种情况允许掩盖中断,那就是你扩展了Thread并因此控制了所有处于调用栈上层的代码。
try{
processTask(queue.take());
} catch (InterruptedException e){
Thread.currentThread().interrupt();
}
Synchronizer
是一个对象,他根据本身的状态调节线程控制流。阻塞队列可以扮演一个
Synchronizer
的角色,其他类型的
Synchronizer
包括信号量
semaphore
、关卡
barrier
以及闭锁
latch
。或者创建一个自己的
Synchronizer
。
闭锁latch是一种Synchronizer,他可以延迟线程的进度知道线程到达终止。一个闭锁工作起来就像一道大门,直到闭锁到达终点状态之前,门一直是关闭的,没有线程能够通过,在终点状态到来的时候,门开了,允许所有线程都通过。一旦闭锁到达了终点状态,他就不能够再改变状态了,所以他会永远保持敞开状态。闭锁可以用来确保特定活动直到其他的活动完成后才发生。
CountDownLatch是一个灵活的闭锁实现,用于上述各种情况,允许一个或多个线程等待一个事件集的发生。闭锁的状态包括一个计数器,初始化为一个正数,用来表现需要等待的事件数,countDown方法对计数器做减操作,表示一个事件已经发生了,而await方法等待计数器达到零,此时所有需要等待的事件都已经发生。如果计数器入口时值为非零,await会一直阻塞直到计数器为零或者等待线程中断以及超时。
public class TestHarness{
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++){
Thread t=new Thread(){
public void run(){
try{
startGate.await();
try{
task.run();
}finally{
endGate.countDown();
}
}catch(InterruptedException ignored){
}
}
}
t.start();
}
long start=System.nanoTime();
startGate.countDown();
endGate.await();
long end=System.nanoTime();
return end-start;
}
}
上面的例子阐述了闭锁的两种常见的用法,他创建了一些线程,并发地执行给定的任务,他使用了两个闭锁,一个开始阀门和一个结束阀门,每一个工作线程要做的第一件事情就是等待开始阀门打开,这样做能确保直到所有线程都做好准备时,才开始工作。每个线程的最后一个工作是结束阀门减一,这样做使控制线程有效地等待,直到最后一个工作线程完成任务,就能计算整个的用时了。
FutureTask同样可以作为闭锁。他的计算是通过Callable实现的,他等价于一个可以携带结果的Runable,并且有3个状态:等待、运行和完成。完成包括所有计算以任意的方式结束包括正常结束、取消和异常。一旦进入完成状态就会永远停止在这个状态上。
Future.get的行为依赖于任务的状态,如果它已经完成,get可以立刻得到返回结果,否则会被阻塞直到任务转入完成状态,然后获得结果或者抛出异常。Future把结果从计算线程传递给需要结果的线程,他的规约保证了这种传递建立在结果的安全发布的基础上。
public class Preloader{
private final FutureTask<ProductInfo> future=
new FutureTask<ProductInfo>(
new Callable<ProductInfo>(){
public ProductInfo call()throws DataLoadException{
return loadProductInfo();
}
}
);
private final Thread thread=new Thread(future);
public void start(){
thread.start();
}
public ProductInfo get()throws DataLoadException,InterruptedException{
try{
return future.get();
}catch(ExecutionException e){
Throwable cause=e.getCause();
if(cause instanceof DataLoadException){
throw (DataLoadException) cause;
}else{
throw launderThrowable(cause);
}
}
}
}
在上面的代码中,如果get抛出了一个ExecutionException,那么原因有以下3中:Callable抛出的受检查的异常、或是一个RuntimeException、或者一个Error。我们必须对这3种情况进行分别处理。
public static RuntimeException launderThrowable(Throwable t){
if(t instanceof RuntimeException){
return (RuntimeException)t;
}else if(t instanceof Error){
throw (Error)t;
}else{
throw new IllegalStateException(“Not unchecked”,t);
}
}
下面的代码讲述了使用信号量来约束容器。
public class BoundedHashSet<T>{
private final Set<T> set;
private final Semaphore sem;
public BoundedHashSet(int bound){
this.set=Collections.synchronizedSet(mew HashSet<T>());
}
public boolean add(T 0)throws InterruptedException{
sem.acquire();
boolean wasAdd=false;
try{
wasAdd=set.add(o);
}finally{
if(!wasAdded){
sem.release();
}
}
}
public boolean remove(Object 0){
boolean wasRemoved=set.remove(o);
if(wasRemoved){
sem.release();
}
return wasRemoved;
}
}
闭锁是一次性使用的对象,一旦进入到最终状态,就不能被重置了。
关卡类似于闭锁,也能够阻塞一组线程,区别在于所有线程必须同时到达关卡点,才能继续处理。闭锁等待的是时间,关卡等待的是其他的线程。
CyclicBarrier允许一个给定数量的成员多次集中在一个关卡点,这在并行迭代算法中非常有用,这个算法会把一个问题拆分成一系列相互独立的子问题。当线程到达关卡点时,调用await,await会被阻塞,直到所有线程都到达关卡点。如果对await的调用超时或者阻塞中的线程被中断,所有对await未完成的调用都通过BrokenBarrierException终止。如果没有异常,await会为每一个线程返回一个唯一的到达索引号。他允许你想构造函数传递一个关卡行为:这是一个Runnable,当成功通过关卡的时候,会(在一个子任务线程中)执行,但是在阻塞线程被释放之前是不能执行的。
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 Runable{
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,computValue(x,y));
}
}
try{
barrier.await();
}catch(InterruptException ex){
return;
}catch(BrokenBarrierException ex){
return;
}
}
}
}
public void start(){
for(int i=0;i<workers.length;i++){
new Thread(workers[i]).start();
}
mainBoard.waitForConvergence();
}
}
在计算问题中,如果不涉及IO或者访问共享数据,Ncpu或者Ncpu+1个线程产生最优吞吐量。