【Java并发】JAVA并发编程实战-读书笔记7

对于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个线程产生最优吞吐量。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值