java并发编程实战-第11章-性能与可伸缩性

java并发编程实战-第11章-性能与可伸缩性

首先要保证程序的正确运行,仅当程序的性能需求和测试结果要求程序执行更快的时候,才应该设法提高程

序的运行速度

11.1 对性能的思考

要想通过并发提高性能,需要做好2件事:

更有效的利用现有的处理资源,以及在出现新的处理资源时使程序尽可能的利用新资源

如果是计算密集型的,则可以添加cpu来处理

11.1.1 性能与可伸缩性

本章重点介绍可伸缩性而不是单线程的性能

11.1.2 评估各种性能权衡因素

避免不成熟的优化。首先使城迅速正确,然后再提高运行速度

已测试为基准,不要猜测

11.2 Amdahl 定律

串行总是存在的,该定律告诉我们,为了提高程序的性能,加速比。需要降低程序中串行部分的比例

11.2.1 示例:在各种框架中隐藏的串行部分

 比较ConcurrentLinkedQueue 与synchronized Linked List

 随着线程数的增加,他们的吞吐率也会提高,但最后都会到达极限,不在增加

11.2.2 Amdahl 定律的应用

如果能准确估计出执行中串行部分所占在比分,则能量化当有多少资源时的加速比

在11.4.2 和11.4.3 节中,介绍的两种降低锁粒度的方法:锁分解和锁分段  。用Amdahl 定律分析,锁分

段更有前途,应为锁分段的数量可以随着处理器数量的增加而增加,串行的比例也会越来越低

11.3  线程引入的开销

11.3.1 上下文切换

    当线程由于锁而被阻塞时,jvm通常会把这个线程挂起,并允许它被交换出去。引起上下文切换,所以

仅从性能考虑,阻塞的地方越少越好,无阻塞算法(15章)有助于上下文切换

11.3.2 内存同步

   区分有竞争的同步和无竞争的同步非常重要。

   syncronized 针对无竞争的同步进行了优化。(volatile通常是非竞争的)

   不要过度担心非竞争同步带来的开销。应重点放在锁竞争的地方。

   

   

   无竞争的同步指加了同步修饰符(syncronized),但实际上该代码是无需同步的 。比如,方法内部对

变量的同步

   比如如下代码 Listing 11.3 :jvm会对同步进行锁消除优化:

   

    Listing 11.3. Candidate for Lock Elision.

public String getStoogeNames() {

   List<String> stooges = new Vector<String>();

   stooges.add("Moe");

   stooges.add("Larry");

   stooges.add("Curly");

   return stooges.toString();

}

  

  

    非竞争的同步可以完全在jvm中处理,而竞争的同步可能需要操作系统的介入,从而增加开销

   所以:重点关注:有竞争的同步

   

11.3.3 阻塞

   

      11.3.1 解释  上下文切换带来开销

   

11.4 减少锁的竞争

    串行操作降低可伸缩性,上下文切换降低性能。在锁上的竞争会同时发生如上两件事情,因此减少锁的

竞争能够提高性能和伸缩性

    

    

    在并发程序中,对可伸缩性最主要的威胁就是独占方式的资源锁

    

    有3种方式:

    1、减少锁的持有时间

    2、降低锁的请求频率

    3、使用带有协调机制的锁,这些机制允许更高的并发性

    

    

11.4.1 缩小锁的范围(“快进快出”)

       比如开发方法调用,改为同步方法内部的代码块

11.4.2 减少锁的粒度

      这可通过锁分解和锁分段技术来实现

      如果一个锁同时保护多个相互独立的状态变量,那么可以将这个锁分解为多个锁 。参考11.6到11.7

的转变

      

      Listing 11.6. Candidate for Lock Splitting.

@ThreadSafe

public class ServerStatus {

   @GuardedBy("this") public final Set<String> users;

   @GuardedBy("this") public final Set<String> queries;

   ...

   public synchronized void addUser(String u) { users.add(u); }

   public synchronized void addQuery(String q) { queries.add(q); }

   public synchronized void removeUser(String u) {

       users.remove(u);

   }

   public synchronized void removeQuery(String q) {

       queries.remove(q);

   }

}

Listing 11.7. ServerStatus Refactored to Use Split Locks.

@ThreadSafe

public class ServerStatus {

   @GuardedBy("users") public final Set<String> users;

   @GuardedBy("queries") public final Set<String> queries;

   ...

   public void addUser(String u) {

       synchronized  (users) { // 使用变量的锁,而不是ServerStatus的

           users.add(u);

       }

   }

   public void addQuery(String q) {

       synchronized  (queries) {//使用变量的锁,而不是ServerStatus的

           queries.add(q);

       }

   }

   // remove methods similarly refactored to use split locks

}

     

11.4.3 锁分段  

     ConcurrentHashMap   参靠list11-8

     

     Listing 11.8. Hash-based Map Using Lock Striping.

@ThreadSafe

public class StripedMap {

   // Synchronization policy: buckets[n] guarded by locks[n%N_LOCKS]

   private static final int N_LOCKS = 16;

   private final Node[] buckets;

   private final Object[] locks;

   private static class Node { ... }

   public StripedMap(int numBuckets) {

       buckets = new Node[numBuckets];

       locks = new Object[N_LOCKS];

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

           locks[i] = new Object();

   }

   private final int hash(Object key) {

       return Math.abs(key.hashCode() % buckets.length);

   }

   public Object get(Object key) {

       int hash = hash(key);

       synchronized (locks[hash % N_LOCKS]) {

           for (Node m = buckets[hash]; m != null; m = m.next)

               if (m.key.equals(key))

                   return m.value;

       }

       return null;

   }

   public void clear() {

       for (int i = 0; i < buckets.length; i++) {

           synchronized (locks[i % N_LOCKS]) {

               buckets[i] = null;

           }

       }

   }

   ...

}

11.4.4  避免热点域 

      一个看似性能优化的措施:缓存size操作的结果,会变成一个可伸缩性的问题。未避免这个问题,

ConcurrentHashMap为每个分段都维护了一个计数器,而不是一个全局的计数器。

      

11.4.5 一些替代独占锁的方法

  放弃独占锁,例如使用并发容器、读写锁、不可变对象以及原子变量

11. 4.6  监测cpu的利用率

 如果cpu未充分利用,通常有以下原因:

 负载不充足

 I/O 密集

 外部限制

 锁竞争

 如果cpu充分利用、并且总有可用的线程在等待cpu,那么当增加更多处理器的时候,程序性能可能会提提

 11.4.7 向对象池说不

       又是一个看似提高性能的措施,实际上会导致可伸缩性问题

       

       通常,对象的分配操作的开销比同步的开销要低

       

       

       

 11.5 比较Map的性能

    并发容器与同步容器

    单线程情况下,性能相当。2个线程,同步容器性能就变得糟糕了,而并发容器性能提高。线程继续增

加,竞争变得激烈,每个操作的时间大部分用于上下文切换和调度延迟,吞吐率不再提高

    

    

11.6 减少上下文切换的开销 

     以日志操作为例子,通过将io操作从处理请求分离出来,可以缩短服务的请求时间。调用log方法的线

程将不会因为等待输入输出流的锁或io完成而被阻塞。

     

     类似锁分解的方法,请求服务的锁和日志记录的锁从一个锁中分离出来。

     

     

小结:

  并发性能的重点通常是吞吐率和可伸缩性,而不是服务时间。Amdahl定律告诉告诉我们,程序的可伸缩性

取决于所有代码中串行执行的代码的比例。而java中串行操作的主要来源是独占方式的资源锁,因此可以通

过以下方式来提升性能

  :减少锁的持有时间、降低锁的粒度、采用非独占锁或非阻塞锁来代替独占锁

  

  

  

猜你喜欢

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