Java并发编程(三) 并发类库中的常用类

1. 同步容器类

  遗留下来的同步容器类包括Vector和Hashtable,此外java.util.Collections类中还提供了以下工厂方法创建线程安全的容器对象:

  Collections.synchronizedList 返回支持同步操作(线程安全)的List对象;

  Collections.synchronizedSet 返回支持同步操作(线程全的)的Set对象;

  Collections.synchronizedMap 返回支持同步操作(线程安全)的Map对象;

  需要注意的是,同步容器类中的每一个单独的方法都是线程安全的,但对这些方法的复合操作仍然需要额外的客户端加锁来保护。另外,同步容器类对象执行每个操作期间都会持有一个锁,这会导致吞吐量大大降低。例如:

List<String>  synList = Collections.synchronizedList(list);

synchronized(synList)
{
   for(int i = 0; i < synList.size(); i++)
     System.out.println(synList.get(i));
}

  对synList对象调用size方法和get方法需要用客户端锁来保护,并且在这个迭代中,需要频繁的加锁和释放锁,吞吐量很低。 使用迭代器对同步容器类对象进行迭代也是需要加锁的,否则在迭代时若有其他线程修改则会抛出ConcurrentModificationException异常。    

 

2. 并发容器类

  并发容器类对同步容器类的性能进行了较大的改进:

  (1)并发容器类提供了一些常用的表示复合操作的方法,并能确保这些复合操作是线程安全的。

    例如:ConcurrentHashMap.putIfAbsent方法,相当于:

      if(!map.contains(key))

        map.put(key,value);

  (2)并发容器类可以大幅度提高并发容器类上操作的吞吐量。

  (3)并发容器类的迭代器和普通的迭代器不一样,这些迭代器具有弱一致性,不具有“fast-fail”特性,不会抛出ConcurrentModificationException。

  常见的并发容器类有:

  ConcurrentLinkedQueue表示并发的队列;BlockingQueue表示阻塞队列,增加了可阻塞的入队和出队操作。如果队列为空,则出队操作阻塞,对于有界队列来说,如果队列满了则入队操作阻塞。阻塞队列可以方便地用于实现“生产者-消费者”模式。

  CopyOnWriteArrayList表示并发的线性表,它通过在每个线程对底层List作出修改时复制整个底层List来实现并发的线程安全,这需要很大的开销,但是对于多线程环境下遍历为主要操作的List来说是很有用的。CopyOnWriteArraySet与CopyOnWriteList类似。

  ConcurrentHashMap表示并发的映射表,ConcurrentHashMap并没有像Hashtable和synchronized的Map一样加独占锁,它使用了更细粒度的分段锁机制,可以大幅提高并发的吞吐量。但是有些方法的返回结果是不准确的,如:size方法和isEmpty方法返回的并不是实时的状态。如果在多线程环境中需要对Map对象独占式的访问,就不应该使用ConcurrentHashMap类。

 

3. 工作密取

  在“生产者—消费者”模式中,生产者和消费者共享一个队列,而在工作密取的情境中,每个消费者都有一个双端队列,在消费者完成了自己队列中的工作时,可以去其他消费者队列的队尾取来工作,而并不会干扰其他消费者的工作。在工作密取情境中,消费者从自己队列的队头取自己的工作,从其他消费者的队尾取别人的工作来完成。

  工作密取非常适合于消费者同时也是生产者的情形,当消费者执行工作时发现有更多的工作要做,则可以将这些工作放到自己队列的末尾,也可以送到其他消费者队列的队尾;当自己队列没有工作要做时,可以去其他消费者队列取工作来完成,这样每个消费者都会保持忙碌的状态。

  工作密取可以使用Deque(双端队列)来完成,ArrayDeque和LinkedList是非阻塞双端队列的实现;BlockingDeque表示阻塞的双端队列,putFirst、takeFirst、putLast、takeLast等方法实现了阻塞地访问队头或者队尾,LinkedBlockingDeque是阻塞双端队列的实现。

 

4. 同步工具类

  同步工具类的作用是根据其自身的状态来协调多线程的运行,同步工具类封装了一些状态,这些状态将决定某些线程是否阻塞,还提供了一些方法对这些状态进行操作。

  常见的同步工具类有:阻塞队列、闭锁、信号量、栅栏。

  (1)闭锁

  闭锁可以延迟线程的进度直到闭锁到达终止状态,可以用来确保某些活动直到其他某个活动都完成后才继续执行。例如:

  确保某个计算在其需要的所有资源都准备好之后才执行;

  确保某个服务在其依赖的所有服务都启动后才启动;

  等待直到某个操作的所有参与者就绪;

  CountDownLatch是常用的闭锁的实现,CountDownLatch包括一个计数器,countDown方法将计数器减1,表示有一个事件发生了,await方法使调用线程阻塞,直到计数器为0,表示等待的所有事件都已经发生。

  FutureTask也可以作为闭锁,FutureTask的计算任务通过Callable来实现,Callable接口有一个call方法,可以返回计算的结果。FutureTask提供了get方法,调用get方法试图获得计算结果,任务已完成则立即获取结果,否则将阻塞直到计算结束。

  (2)信号量

  信号量可以用来控制同时访问某个资源的线程数量,可以用来实现资源池。

  Semaphore是信号量的实现,Semaphore维护着一定数量的许可,在执行操作时可以首先通过acquire方法获得许可,如果许可已经用完则阻塞,操作执行完后通过release方法释放许可。

  许可数目为1的信号量可以作为互斥信号量,并且是不可重入的。

  (3)栅栏

  栅栏和闭锁很相似,主要区别在于栅栏延迟线程的进度直到所有线程都准备好以后。

  CyclicBarrier是栅栏的实现,线程调用await方法到达栅栏处,若该线程不是到达栅栏处的最后一个线程,则被阻塞。若该线程是到达栅栏处的最后一个线程,则不会阻塞,并且栅栏释放,所有的线程都可以继续执行。

  所有线程都释放之后,栅栏将被重置,栅栏非常适合于并行迭代算法。

  

 

参考资料 《Java并发编程实战》

posted @ 2015-09-02 21:36  jqc  阅读(3524)  评论(0编辑  收藏  举报