Plumbr是唯一通过解释应用程序性能数据来自动检测Java性能问题的根本原因的解决方案。

几个月前,我们在Plumbr中引入了锁定线程检测之后,我们开始收到类似于“嘿,太好了,现在我知道是什么导致了性能问题,但是现在应该做什么?”这样的查询。

我们正在努力将解决方案说明构建到我们自己的产品中,但是在这篇文章中,我将分享几种独立于检测锁的工具可以应用的常见技术。 这些方法包括锁拆分,并发数据结构,保护数据(而不是代码)和减少锁范围。

锁不是邪恶的,锁争用是

每当您遇到线程代码的性能问题时,就有机会开始指责锁。 毕竟,常见的“知识”是锁很慢并且限制了可伸缩性。 因此,如果您具备了这种“知识”并开始优化代码并摆脱了锁,那么您最终有可能引入令人讨厌的并发错误,这些错误将在以后出现。

因此,了解竞争锁和非竞争锁之间的区别非常重要。 当一个线程试图进入另一个线程当前执行的同步块/方法时,发生锁争用。 现在,第二个线程被迫等待,直到第一个线程完成执行同步块并释放监视器。 当一次仅一个线程试图执行同步代码时,锁保持无竞争状态。

实际上,JVM中的同步针对无竞争的情况进行了优化,并且对于绝大多数应用程序而言,无竞争的锁几乎在执行期间没有开销。 因此,它不是性能应归咎的锁,而是争执的锁。 有了这些知识,让我们看看如何减少争用的可能性或减少争用的时间。

保护数据而不是代码

实现线程安全的一种快速方法是锁定对整个方法的访问。 例如,请看以下示例,该示例说明了构建在线扑克服务器的幼稚尝试:

class GameServer {public Map<<String, List<Player>> tables = new HashMap<String, List<Player>>();public synchronized void join(Player player, Table table) {if (player.getAccountBalance() > table.getLimit()) {List<Player> tablePlayers = tables.get(table.getId());if (tablePlayers.size() < 9) {tablePlayers.add(player);}}}public synchronized void leave(Player player, Table table) {/*body skipped for brevity*/}public synchronized void createTable() {/*body skipped for brevity*/}public synchronized void destroyTable(Table table) {/*body skipped for brevity*/}
}

作者的意图一直很好–当新玩家加入桌台时,必须保证坐在桌旁的玩家人数不会超过9人。

但是,只要这样的解决方案实际上负责使玩家坐在桌子上,即使是在流量适中的扑克场所,该系统注定要通过等待释放锁的线程不断触发争用事件。 锁定块包含帐户余额和表限制检查,这可能会涉及昂贵的操作,从而增加争用的可能性和持续时间。

解决方案的第一步是通过将同步从方法声明移到方法主体,确保我们保护数据,而不是代码。 在上面的简约示例中,它起初可能没有太大变化。 但是让我们考虑整个GameServer接口,而不仅仅是单个join()方法:

class GameServer {public Map<String, List<Player>> tables = new HashMap<String, List<Player>>();public void join(Player player, Table table) {synchronized (tables) {if (player.getAccountBalance() > table.getLimit()) {List<Player> tablePlayers = tables.get(table.getId());if (tablePlayers.size() < 9) {tablePlayers.add(player);}}}}public void leave(Player player, Table table) {/* body skipped for brevity */}public void createTable() {/* body skipped for brevity */}public void destroyTable(Table table) {/* body skipped for brevity */}
}

原本似乎是很小的更改,但现在影响了整个班级的行为。 每当玩家加入表时,先前同步的方法就会锁定在GameServer实例( this )上,并向试图同时离开 table ()表的玩家引入争用事件。 将锁从方法签名移到方法主体可推迟锁定并减少争用可能性。

缩小锁定范围

现在,在确保它是我们实际保护的数据而不是代码之后,我们应该确保我们的解决方案仅锁定必要的内容,例如,当上面的代码被重写如下时:

public class GameServer {public Map<String, List<Player>> tables = new HashMap<String, List<Player>>();public void join(Player player, Table table) {if (player.getAccountBalance() > table.getLimit()) {synchronized (tables) {List<Player> tablePlayers = tables.get(table.getId());if (tablePlayers.size() < 9) {tablePlayers.add(player);}}}}//other methods skipped for brevity
}

那么检查玩家帐户余额的潜在耗时操作(可能涉及IO操作)现在已超出锁定范围。 请注意,引入该锁仅是为了防止超出表的容量,并且帐户余额检查也不是该保护措施的一部分。

分开锁

当我们看最后一个代码示例时,您可以清楚地注意到整个数据结构都受到同一锁的保护。 考虑到我们可能在这种结构中容纳成千上万张扑克桌,由于我们必须分别保护每张桌子以防容量溢出,因此它仍然会带来争用事件的高风险。

为此,有一种简单的方法可以在每个表中引入单个锁,例如以下示例:

public class GameServer {public Map<String, List<Player>> tables = new HashMap<String, List<Player>>();public void join(Player player, Table table) {if (player.getAccountBalance() > table.getLimit()) {List<Player> tablePlayers = tables.get(table.getId());synchronized (tablePlayers) {if (tablePlayers.size() < 9) {tablePlayers.add(player);}}}}//other methods skipped for brevity
}

现在,如果我们仅同步访问同一而不是所有表的访问 ,则可以大大降低锁争用的可能性。 例如,在我们的数据结构中有100个表,争用的可能性现在比以前小100倍。

使用并发数据结构

另一个改进是删除传统的单线程数据结构,并使用为并行使用而明确设计的数据结构。 例如,当选择ConcurrentHashMap来存储所有扑克表时,将导致类似于以下代码:

public class GameServer {public Map<String, List<Player>> tables = new ConcurrentHashMap<String, List<Player>>();public synchronized void join(Player player, Table table) {/*Method body skipped for brevity*/}public synchronized void leave(Player player, Table table) {/*Method body skipped for brevity*/}public synchronized void createTable() {Table table = new Table();tables.put(table.getId(), table);}public synchronized void destroyTable(Table table) {tables.remove(table.getId());}
}

由于我们需要保护单个表的完整性,因此join()leave()方法中的同步仍然像我们之前的示例中那样。 因此, ConcurrentHashMap在这方面没有帮助。 但是,由于我们还在创建新表并在createTable()destroyTable()方法中销毁表,因此对ConcurrentHashMap的所有这些操作都是完全并发的,从而允许增加或减少并行表的数量。

其他提示和技巧

  • 降低锁的可见性。 在上面的示例中,这些锁被声明为公共锁,因此对于世界都是可见的,因此有可能其他人也会通过锁定您精心挑选的监视器来破坏您的工作。
  • 请查看java.util.concurrent.locks,以查看在那里实施的任何锁定策略是否都会改善解决方案。
  • 使用原子操作。 我们在上面的示例中实际进行的简单计数器增加实际上并不需要锁定。 用AtomicInteger替换计数跟踪中的Integer最适合此示例。

希望本文能帮助您解决锁争用问题,而无论您使用的是Plumbr 自动锁检测解决方案还是从线程转储中手动提取信息。

Plumbr是唯一通过解释应用程序性能数据来自动检测Java性能问题的根本原因的解决方案。

翻译自: https://www.javacodegeeks.com/2015/01/improving-lock-performance-in-java.html

提高Java的锁性能相关推荐

  1. java锁性能对比_提高Java的锁性能

    java锁性能对比 Plumbr是唯一可以通过解释应用程序性能数据来自动检测Java性能问题根本原因的解决方案. 几个月前,我们在Plumbr中引入了锁定线程检测之后,我们开始收到类似于"嘿 ...

  2. 如何提高 Java 中锁的性能

    锁不是问题的根源,锁之间的竞争才是 通常在多线程的代码中遇到性能方面的问题时,一般都会抱怨是锁的问题.毕竟锁会降低程序的运行速度和其较低的扩展性是众所周知的.因此,如果带着这种"常识&quo ...

  3. Ways to 优化JAVA程序设计和编码,提高JAVA性能

    通过使用一些辅助性工具来找到程序中的瓶颈,然后就可以对瓶颈部分的代码进行优化.一般有两种方案:即优化代码或更改设计方法.我们一般会选择后者,因为不去调用以下代码要比调用一些优化的代码更能提高程序的性能 ...

  4. 如何用JNI技术提高Java的性能详解

    阻碍Java获得广泛应用的一个主要因素是Java程序的运行效率.Java是介于解释型和编译型之间的一种语言,同样的程序,如果用编译型语言C来实现,其运行速度一般要比Java快一倍以上.Java具有平台 ...

  5. java lock 效率_工作常用4种Java线程锁的特点,性能比较、使用场景

    多线程的缘由 在出现了进程之后,操作系统的性能得到了大大的提升.虽然进程的出现解决了操作系统的并发问题,但是人们仍然不满足,人们逐渐对实时性有了要求. 使用多线程的理由之一是和进程相比,它是一种非常花 ...

  6. Java多线程系列(四):4种常用Java线程锁的特点,性能比较、使用场景

    多线程的缘由 在出现了进程之后,操作系统的性能得到了大大的提升.虽然进程的出现解决了操作系统的并发问题,但是人们仍然不满足,人们逐渐对实时性有了要求. 使用多线程的理由之一是和进程相比,它是一种非常花 ...

  7. 【Spring】高并发下如何提高“锁”性能?

    高并发下如何提高"锁"性能? 前言 减小锁持有时间 减小锁粒度 读写分离锁来替换独占锁 锁分离 锁粗化 总结 前言 在项目中,尤其是电商或者做游戏开发的,高并发是必然的,但在高并发 ...

  8. 性能调优之Java系统级性能监控及优化

    性能调优之Java系统级性能监控及优化 对于性能调优而言,通常我们需要经过以下三个步骤:1,性能监控:2,性能剖析:3,性能调优 性能调优:通过分析影响Application性能问题根源,进行优化Ap ...

  9. JAVA偏向锁的什么时候释放_Java中的偏向锁

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 Java偏向锁(Biased Locking)是Java6引入的一项多线程优化. 偏向锁,顾名思义,它会偏向于第一个访问锁的线程,如果在运行过程中,同步锁 ...

最新文章

  1. linux内核之内存管理.doc,linux内核之内存管理.doc
  2. UVA - 12083 Guardian of Decency (二分匹配)
  3. [设计模式] 15.Command 命令模式
  4. SQL数据库 - (二)关系数据库
  5. 机械零点、MAM 文件 、 EMT标定原理
  6. 【Linux】linux服务器下的帮助命令总结
  7. 【EISCI征稿中】上海 · 首届长三角人工智能产业发展论坛AINIT2020
  8. 云存储服务器销售,云存储服务器销售
  9. 四、Angular新建组件和使用
  10. 数据层(DAL)自动生成库
  11. JAVA学习第六十三课 — 关于client服务端 amp;amp; URL类 amp; URLConnection
  12. AIDL的简单使用和注意事项
  13. 轻量级的java HTTP Server——NanoHttpd
  14. 两种自动化测试工具AutoRunner与Selenium的对比
  15. CSDN日报20170602 ——《程序员、技术主管和架构师》
  16. RTB实时竞价, 重塑网络媒体交易规则
  17. 动态改变Input和Textarea值Vue数据没有绑定的解决办法
  18. 京东广告推荐机器学习系统实践
  19. 学习java第14天
  20. 如何用HTML和css实现简单的手风琴菜单效果,附带详细注释

热门文章

  1. 普里姆算法(修路问题)+图解
  2. spark submit参数及调优
  3. php破坏代码,php不破坏单词截取子字符串
  4. 2-6 基于SpringBoot的SpringSecurity环境快速搭建与验证
  5. jakarta ee_MicroProfile在Jakarta EE时代的作用
  6. graalvm_GraalVM上的Picocli:极快的命令行应用程序
  7. glacier2_Amazon Glacier的Scala客户端
  8. oauth2令牌刷新_了解OAuth2令牌认证
  9. activiti异步执行_对基于消息队列的Activiti异步执行器进行基准测试
  10. storm apache_Apache Storm的实时情绪分析示例