• Java多线程性能优化


    大家使用多线程无非是为了提高性能,但如果多线程使用不当,不但性能提升不明显,而且会使得资源消耗更大。下面列举一下可能会造成多线程性能问题的点:

    • 死锁

    • 过多串行化

    • 过多锁竞争

    • 切换上下文

    • 内存同步

    下面分别解析以上性能隐患

    死锁

    关于死锁,我们在学习操作系统的时候就知道它产生的原因和危害,这里就不从原理上去累述了,可以从下面的代码和图示重温一下死锁产生的原因:

    [java] view plaincopy

    1. public class LeftRightDeadlock {  

    2.     private final Object left = new Object();  

    3.     private final Object right = new Object();  

    4.     public void leftRight() {  

    5.         synchronized (left) {  

    6.             synchronized (right) {  

    7.                 doSomething();  

    8.             }  

    9.         }  

    10.     }  

    11.     public void rightLeft() {  

    12.         synchronized (right) {  

    13.             synchronized (left) {  

    14.                 doSomethingElse();  

    15.             }  

    16.         }  

    17.     }  

    18. }  

    预防和处理死锁的方法:

    1)尽量不要在释放锁之前竞争其他锁

    一般可以通过细化同步方法来实现,只在真正需要保护共享资源的地方去拿锁,并尽快释放锁,这样可以有效降低在同步方法里调用其他同步方法的情况

    2)顺序索取锁资源

    如果实在无法避免嵌套索取锁资源,则需要制定一个索取锁资源的策略,先规划好有哪些锁,然后各个线程按照一个顺序去索取,不要出现上面那个例子中不同顺序,这样就会有潜在的死锁问题

    3)尝试定时锁

    Java 5提供了更灵活的锁工具,可以显式地索取和释放锁。那么在索取锁的时候可以设定一个超时时间,如果超过这个时间还没索取到锁,则不会继续堵塞而是放弃此次任务,示例代码如下:

    [java] view plaincopy

    1. public boolean trySendOnSharedLine(String message,  

    2.                                    long timeout, TimeUnit unit)  

    3.                                    throws InterruptedException {  

    4.     long nanosToLock = unit.toNanos(timeout)  

    5.                      - estimatedNanosToSend(message);  

    6.     if (!lock.tryLock(nanosToLock, NANOSECONDS))  

    7.         return false;  

    8.     try {  

    9.         return sendOnSharedLine(message);  

    10.     } finally {  

    11.         lock.unlock();  

    12.     }  

    13. }  

    这样可以有效打破死锁条件。

    4)检查死锁

    JVM采用thread dump的方式来识别死锁的方式,可以通过操作系统的命令来向JVM发送thread dump的信号,这样可以查询哪些线程死锁。

    过多串行化

    用多线程实际上就是想并行地做事情,但这些事情由于某些依赖性必须串行工作,导致很多环节得串行化,这实际上很局限系统的可扩展性,就算加CPU加线程,但性能却没有线性增长。有个Amdahl定理可以说明这个问题:

    其中,F是串行化比例,N是处理器数量,由上可知,只有尽可能减少串行化,才能最大化地提高可扩展能力。降低串行化的关键就是降低锁竞争,当很多并行任务挂在锁的获取上,就是串行化的表现

    过多锁竞争

    过多锁竞争的危害是不言而喻的,那么看看有哪些办法来降低锁竞争

    1)缩小锁的范围

    前面也谈到这一点,尽量缩小锁保护的范围,快进快出,因此尽量不要直接在方法上使用synchronized关键字,而只是在真正需要线程安全保护的地方使用

    2)减小锁的粒度

    Java 5提供了显式锁后,可以更为灵活的来保护共享变量。synchronized关键字(用在方法上)是默认把整个对象作为锁,实际上很多时候没有必要用这么大一个锁,这会导致这个类所有synchronized都得串行执行。可以根据真正需要保护的共享变量作为锁,也可以使用更为精细的策略,目的就是要在真正需要串行的时候串行,举一个例子:

    [java] view plaincopy

    1. public class StripedMap {  

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

    3.     private static final int N_LOCKS = 16;  

    4.     private final Node[] buckets;  

    5.     private final Object[] locks;  

    6.     private static class Node { ... }  

    7.     public StripedMap(int numBuckets) {  

    8.         buckets = new Node[numBuckets];  

    9.         locks = new Object[N_LOCKS];  

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

    11.             locks[i] = new Object();  

    12.     }  

    13.     private final int hash(Object key) {  

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

    15.     }  

    16.     public Object get(Object key) {  

    17.         int hash = hash(key);  

    18.         synchronized (locks[hash % N_LOCKS]) {  

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

    20.                 if (m.key.equals(key))  

    21.                     return m.value;  

    22.         }  

    23.         return null;  

    24.     }  

    25.     public void clear() {  

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

    27.             synchronized (locks[i % N_LOCKS]) {  

    28.                 buckets[i] = null;  

    29.             }  

    30.         }  

    31.     }  

    32.     ...  

    33. }  

    上面这个例子是通过hash算法来把存取的值所对应的hash值来作为锁,这样就只需要对hash值相同的对象存取串行化,而不是像HashTable那样对任何对象任何操作都串行化。

    3)减少共享资源的依赖

    共享资源是竞争锁的源头,在多线程开发中尽量减少对共享资源的依赖,比如对象池的技术应该慎重考虑,新的JVM对新建对象以做了足够的优化,性能非常好,如果用对象池不但不能提高多少性能,反而会因为锁竞争导致降低线程的可并发性。

    4)使用读写分离锁来替换独占锁

    Java 5提供了一个读写分离锁(ReadWriteLock)来实现读-读并发,读-写串行,写-写串行的特性。这种方式更进一步提高了可并发性,因为有些场景大部分是读操作,因此没必要串行工作。关于ReadWriteLock的具体使用可以参加一下示例:

    [java] view plaincopy

    1. public class ReadWriteMap<K,V> {  

    2.     private final Map<K,V> map;  

    3.     private final ReadWriteLock lock = new ReentrantReadWriteLock();  

    4.     private final Lock r = lock.readLock();  

    5.     private final Lock w = lock.writeLock();  

    6.     public ReadWriteMap(Map<K,V> map) {  

    7.         this.map = map;  

    8.     }  

    9.     public V put(K key, V value) {  

    10.         w.lock();  

    11.         try {  

    12.             return map.put(key, value);  

    13.         } finally {  

    14.             w.unlock();  

    15.         }  

    16.     }  

    17.     // Do the same for remove(), putAll(), clear()  

    18.     public V get(Object key) {  

    19.         r.lock();  

    20.         try {  

    21.             return map.get(key);  

    22.         } finally {  

    23.             r.unlock();  

    24.         }  

    25.     }  

    26.     // Do the same for other read-only Map methods  

    27. }  

    切换上下文

    线程比较多的时候,操作系统切换线程上下文的性能消耗是不能忽略的,在构建高性能web之路------web服务器长连接 可以看出在进程切换上的代价,当然线程会更轻量一些,不过道理是类似的

    内存同步

    当使用到synchronized、volatile或Lock的时候,都会为了保证可见性导致更多的内存同步,这就无法享受到JMM结构带来了性能优化。

  • 相关阅读:
    恢复更新
    Sun与Google结为战略合作伙伴, 可能联合Sun推出Web Office
    Outlook数据文件备份和按年存档的办法
    新装的几个软件试用体会(WPS 2005,Opera,QQ 2005 Beta3)
    关于VBA和Excel,GIS软件
    人格测试
    Taking a serious look at grids
    iPod、iMac 5、iTunes 6
    如此“自助”
    UCGIS优先研究领域介绍
  • 原文地址:https://www.cnblogs.com/toSeeMyDream/p/5537796.html
Copyright © 2020-2023  润新知