java并发编程实战-第5章-基础构建模块

java并发编程实战-第5章-基础构建模块

  第4章中,介绍的构造线程安全类中的技术中,委托是创建线程安全类的一个最有效策略。

  java平台类中包含了丰富的并发基础构建模块,例如线程安全类容器和同步类工具。本章将介绍其中的一些最有用的模块以及使用这些模块构建并发应用程序的一些常用模式

  

  

5.1 同步容器类

    早期的有vector、Hashtable 、以及Collections.synchronizedXxx构建的容器

    

5.1.1 同步容器类的问题

   同步类容器是线程安全的,但在某些情况下需要客户端加锁来保护复合操作。比如常见的迭代

   

   list 5-1 在Vector上可能导致混乱结果的复合操作 

   public static Object getLast(Vector list) {

    int lastIndex = list.size() - 1;

    return list.get(lastIndex);

}

public static void deleteLast(Vector list) {

   int lastIndex = list.size() - 1;

   list.remove(lastIndex);

}

    A ->size(10)------------->get(9)->出错   

    B ->size(10)->remove(9)

    

    在客户端加锁解决如上问题

    public static Object getLast(Vector list) {

    synchronized (list) {

        int lastIndex = list.size() - 1;

        return list.get(lastIndex);

    }

}

public static void deleteLast(Vector list) {

   synchronized (list) {

       int lastIndex = list.size() - 1;

       list.remove(lastIndex);

   }

}

    

    

    

    

5.1.2 Iterators and Concurrentmodificationexception

List<Widget> widgetList

   = Collections.synchronizedList(new ArrayList<Widget>());

...

// May throw ConcurrentModificationException

for (Widget w : widgetList)

   doSomething(w);

  为解决这个问题,则可以再迭代期间对容器加锁,但是容器内容很多的情况下,那么持有锁的时间会变长,在锁上的竞争更激烈。如果不希望在迭代期间对容器加锁,那么

  一种替代方法是"克隆"容器

  

  

5.1.3 隐藏的迭代器

   

   像set.toString(),containsAll、removeAll和retainAll等方法,在这些间接的迭代操作都可能抛出Concurrentmodificationexception

   

   

   例子: 

public class HiddenIterator {

   @GuardedBy("this")

   private final Set<Integer> set = new HashSet<Integer>();

   public synchronized void add(Integer i) { set.add(i); }

   public synchronized void remove(Integer i) { set.remove(i); }

   public void addTenThings() {

       Random r = new Random();

       for (int i = 0; i < 10; i++)

           add(r.nextInt());

       System.out.println("DEBUG: added ten elements to " + set);   //隐藏的迭代器

  }

}

If HiddenIterator wrapped the HashSet with a synchronizedSet, encapsulating the synchronization, this sort of error would not occur.

5.2 并发容器 

    java5.0 提供了很多并发容器来改进同步容器的性能。

    

    通过并发容器代替同步容器,可以极大地提高伸缩性并降低风险

    

    ConcurrentHashMap 

    CopyOnWriteArrayList 

    Queue 实现有传统的FIFO队列ConcurrentLinkedQueue 、优先队列 PriorityQueue ,这些队列的操作不会阻塞、如果没有数据则返回null

    and BlockingQueue,扩展了Queue,如果为空,则阻塞等待。在生产者-消费者模式中非常有用(5.3介绍)

    

5.2.1 ConcurrentHashMap

     分段锁机制

     可以容忍并发的修改,size和isEmpty 是估计值,但是这些方法在并发环境中作用很小,因为他们的返回值总在变化。这些操作的需求被弱化了,换取了其他更重要的操作的性能优化,比如put、get、containKey、remove等

     

5.2.2 额外的原子Map操作

      a number of common compound operations such as put-if-absent, remove-if-equal, and replace-if-equal are implemented as atomic operations and specified by the ConcurrentMap interface

     如果需要在同步容器中添加这些操作,则需要考虑是否需要ConcurrentMap

5.2.3. CopyOnWriteArrayList

     写入时复制ArrayList,底层指向一个不可变的数组,迭代时只要保证可见性即可。每次修改时,都会创建并重新发布一个新的容器副本(复制底层数组)。

     所以仅当迭代操作远远多于修改操作时才应该使用CopyOnWriteArrayList,比如事件通知系统

5.3  阻塞队列和生产者消费者模式

     一种最常见的生产者-消费者设计模式就是线程池与工作队列的组合

     

     Bounded queues are a powerful resource management tool for building reliable applications: 

     they make your program more robust to overload by throttling activities that threaten to produce more work than can be handled.

5.3.1 示例:桌面搜索

      生产者-消费者 阻塞队列 的应用

      

5.3.2 串行线程封闭

      生产者-消费者设计与阻塞队列一起,促进了线程封闭,从而将对象的所有权从生产者交给消费者

      

5.3.3 双端队列与工作密取(Deques and Work Stealing)

      

       在生产者-消费者设计,所有消费者共享一个队列

       在工作密取设计中,每个消费者都有各自的双端队列。

       most of the time they access only their own deque, reducing contention. When a worker has to access another's queue, it does so from the tail rather than the head, further reducing contention

       

       

       Work stealing is well suited to problems in which consumers are also producerswhen performing a unit of work is likely to result in the identification of more work.

      

     例如,网页爬虫处理一个页面时会发现更多的页面需要处理。类似的搜索图算法

     

5.4 阻塞方法与中断方法

    出现InterruptedException,代码必须对此进行处理。对于类库来说,2中选择

    1、传递InterruptedException

    2、恢复中断

    

    3、捕获但不处理 (特殊情况下使用,即对Thread进行扩展,并且能调用栈上所有高层的代码,。第7章进一步介绍取消和中断)

5.5 同步工具

   Blocking queues

           

   semaphores 信号量

      Counting semaphores(计数信号量) are used to control the number of activities that can access a certain resource or perform a given action at the same time [CPJ 3.4.1]. 

      例如:资源池、BoundedHashSet 

   barriers 栅栏

     Barriers are similar to latches in that they block a group of threads until some event has occurred [CPJ 4.4.3]. 

     The key difference is that with a barrier, all the threads must come together at a barrier point at the same time in order to proceed. Latches are for waiting for events; barriers are for waiting for other threads

     barriers等其他线程

     latches 等事件

     CyclicBarrier 的应用

  

   latches 闭锁

      CountDownLatch  参考jdk文档例子

      

5.5.2 FutureTask  (also acts like a latch)

      Future.get(); The behavior of Future.get depends on the state of the task. If it is completed, get returns the result immediately, and otherwise blocks until the task transitions to the completed state and then returns the result or throws an exception

5.5.4 semaphores

5.5.4 barriers

   应用1:并行迭代算法 例子:细胞自动化模拟

     

   应用2:Exchanger   参考jdk自带例子

5.6 构建高效且可伸缩的结果缓存

    改进过程:

    HashMap ->

    ConcurrentHashMap<A, V>(); ->

    -new ConcurrentHashMap<A, Future<V>>();

    f = cache.putIfAbsent(arg, ft); 代替 cache.put(arg, ft);

    

    ==》Factorizing Servlet that Caches Results Using Memoizer

第1部分小结:

1、It's the mutable state, stupid. [1]

All concurrency issues boil down to coordinating access to mutable state. The less mutable state, the easier it is to ensure thread safety.

2、Make fields final unless they need to be mutable.

3、Immutable objects are automatically thread-safe.

Immutable objects simplify concurrent programming tremendously. They are simpler and safer, and can be shared freely without locking or defensive copying.

4、Encapsulation makes it practical to manage the complexity.

You could write a thread-safe program with all data stored in global variables, but why would you want to? 

Encapsulating data within objects makes it easier to preserve their invariants; encapsulating synchronization within objects makes it easier to comply with their synchronization policy.

5、Guard each mutable variable with a lock.

6、 Guard all variables in an invariant with the same lock.

7、Hold locks for the duration of compound actions.

8、A program that accesses a mutable variable from multiple threads without synchronization is a broken program.

9、Don't rely on clever reasoning about why you don't need to synchronize.

10、Include thread safety in the design processor explicitly document that your class is not thread-safe.

11、Document your synchronization policy.

  

猜你喜欢

转载自zhouchaofei2010.iteye.com/blog/2243280