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

  • 死锁
  • 过多串行化
  • 过多锁竞争
  • 切换上下文
  • 内存同步

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

死锁

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

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

  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都得串行执行。可以根据真正需要保护的共享变量作为锁,也可以使用更为精细的策略,目的就是要在真 正需要串行的时候串行,举一个例子:

  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的具体使用可以参加一下示例:

  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结构带来了性能优化。

探索并发编程(六)------Java多线程性能优化的更多相关文章

  1. Java多线程性能优化

    大家使用多线程无非是为了提高性能,但如果多线程使用不当,不但性能提升不明显,而且会使得资源消耗更大.下面列举一下可能会造成多线程性能问题的点: 死锁 过多串行化 过多锁竞争 切换上下文 内存同步 下面 ...

  2. Java的性能优化

    http://www.toutiao.com/i6368345864624144897/?tt_from=mobile_qq&utm_campaign=client_share&app ...

  3. [JAVA] java程序性能优化

    一.避免在循环条件中使用复杂表达式 在不做编译优化的情况下,在循环中,循环条件会被反复计算,如果不使用复杂表达式,而使循环条件值不变的话,程序将会运行的更快. 例子: import java.util ...

  4. 从虚拟机视角谈 Java 应用性能优化

    从虚拟机视角谈 Java 应用性能优化 周 祥, 软件工程师, IBM 简介:Java 的普及和广泛应用,以及其基于虚拟机运行的机制,使得性能问题越来越重要.本文从 Java 虚拟机的角度,特别是垃圾 ...

  5. Java程序性能优化技巧

    Java程序性能优化技巧 多线程.集合.网络编程.内存优化.缓冲..spring.设计模式.软件工程.编程思想 1.生成对象时,合理分配空间和大小new ArrayList(100); 2.优化for ...

  6. Java GC 专家系列5:Java应用性能优化的原则

    本文是GC专家系列中的第五篇.在第一篇理解Java垃圾回收中我们学习了几种不同的GC算法的处理过程,GC的工作方式,新生代与老年代的区别.所以,你应该已经了解了JDK 7中的5种GC类型,以及每种GC ...

  7. Python并发编程系列之多线程

    1 引言 上一篇博文详细总结了Python进程的用法,这一篇博文来所以说Python中线程的用法.实际上,程序的运行都是以线程为基本单位的,每一个进程中都至少有一个线程(主线程),线程又可以创建子线程 ...

  8. 【并发编程】Java并发编程传送门

    本博客系列是学习并发编程过程中的记录总结.由于文章比较多,写的时间也比较散,所以我整理了个目录贴(传送门),方便查阅. [并发编程系列博客传送门](https://www.cnblogs.com/54 ...

  9. 《Java程序性能优化:让你的Java程序更快、更稳定》

    Java程序性能优化:让你的Java程序更快.更稳定, 卓越网更便宜,不错的书吧

随机推荐

  1. 利用Fidder工具抓取App数据包

    第一步:下载神器Fiddler,下载链接: http://fiddler2.com/get-fiddler 下载完成之后,傻瓜式的安装一下了! 第二步:设置Fiddler打开Fiddler,     ...

  2. base64的作用

    本函数将字符串以 MIME BASE64 编码.此编码方式可以让中文字或者图片也能在网络上顺利传输.在 BASE64 编码后的字符串只包含英文字母大小写.阿拉伯数字.加号与反斜线,共 64 个基本字符 ...

  3. 【转】EventBus 3.0使用详解

    原文:https://www.jianshu.com/p/f9ae5691e1bb 01 前言 当我们进行项目开发的时候,往往是需要应用程序的各组件.组件与后台线程间进行通信,比如在子线程中进行请求数 ...

  4. 什么是APS高级计划排程(生产计划排产)系统主要功能模块有哪些?

    什么是APS高级计划排程(生产计划排产)系统? APS高级计划排程(高级计划排产)系统主要解决“在有限产能条件下,交期产能精确预测.工序生产与物料供应最优详细计划”的问题.APS高级计划排程(高级计划 ...

  5. Robot Framework + Selenium2Lib

    Robot Framework + Selenium2Lib 最近一段时间,公司在推行自动化测试流程,本人有幸参与了自定义通用控件的关键字封装和脚本辅助编写.数据驱动管理.测试用例执行管理等一系列工具 ...

  6. LeetCode 628. 三个数的最大乘积

    题目描述 LeetCode 628. 三个数的最大乘积 给定一个整型数组,在数组中找出由三个数组成的最大乘积,并输出这个乘积. 示例1 输入: [1,2,3] 输出: 6 示例2 输入: [1,2,3 ...

  7. cvc-complex-type.2.4.d: 发现了以元素 'd:skin' 开头的无效内容。此处不应含有子元素。

      Eclipse上开发Android的时候,新建的项目提示: [2016-01-13 14:07:56 - android SDK] Error when loading the SDK: Erro ...

  8. 解决win10下git闪退

    网上找了很多方法,只有这个是有用的,记录下来. 问题描述 在git官网下载了软件,安装之后,git Bash出现闪退现象,同时在当前文件夹下面会生成一个mintty.exe.stackdump的文件. ...

  9. WSDL语法

    <什么是WSDL语言> WSDL(网络服务描述语言,Web Services Description Language)是一门基于 XML 的语言,用于描述 Web Services 以及 ...

  10. 韩梦飞沙-屏幕录像专家 win10 含注册机

    韩梦飞沙  韩亚飞  313134555@qq.com  yue31313  han_meng_fei_sha 韩梦飞沙-屏幕录像专家 win10 含注册机 百度云盘下载地址:https://pan. ...