java 中锁的性能提高办法

发布时间 - 2026-01-10 23:07:21    点击率:

java 中锁的性能提高办法

我们努力为自己的产品所遇到的问题思考解决办法,但在这篇文章中我将给大家分享几种常用的技术,包括分离锁、并行数据结构、保护数据而非代码、缩小锁的作用范围,这几种技术可以使我们不使用任何工具来检测死锁。

锁不是问题的根源,锁之间的竞争才是

通常在多线程的代码中遇到性能方面的问题时,一般都会抱怨是锁的问题。毕竟锁会降低程序的运行速度和其较低的扩展性是众所周知的。因此,如果带着这种“常识”开始优化代码,其结果很有可能是在之后会出现讨人厌的并发问题。

因此,明白竞争锁和非竞争锁的不同是非常重要的。当一个线程试图进入 另一个线程正在执行的同步块或方法时会触发锁竞争。该线程会被强制进入等待状态,直到第一个线程执行完同步块并且已经释放了监视器。当同一时间只有一个线 程尝试执行同步的代码区域时,锁会保持非竞争的状态。

事实上,在非竞争的情况下和大多数的应用中,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。

但是这种解决办法事实上无论何时都要对玩家进入牌桌进行控制——即使是在服务器的访问量较小的时候也是这样,那些等 待锁释放的线程注定会频繁的触发系统的竞争事件。包含对账户余额和牌桌限制检查的锁定块很可能大幅提高调用操作的开销,而这无疑会增加竞争的可能性和持续 时间。

解决的第一步就是确保我们保护的是数据,而不是从方法声明移到方法体中的那段同步声明。对于上面那个简单的例子来说,可能改变不大。但是我们要站在整个游戏服务的接口之上来考虑,而不是单单的一个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实例加锁,进而会与那些同时试图离开牌桌的玩家产生竞争。将锁从方法声明移到方法体中会延迟锁的加载,进而降低了锁竞争的可能性。

缩小锁的作用范围

现在,当确信了需要保护的是数据而非程序后,我们应该确保我们只在必要的地方加锁——例如当上面的代码被重构之后:

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 在这点上并没有任何帮助。但我们仍然会在increateTable()和destoryTable()方法中使用ConcurrentHashMap创建和销毁新的牌桌,所有这些操作对于ConcurrentHashMap来说是完全同步的,其允许我们以并行的方式添加或减少牌桌的数量。

其他一些建议和技巧

降低锁的可见度。在上面的例子中,锁被声明为public(对外可见),这可能会使得一些别有用心的人通过在你精心设计的监视器上加锁来破坏你的工作。

通过查看java.util.concurrent.locks 的API来看一下 有没有其它已经实现的锁策略,使用其改进上面的解决方案。

使用原子操作。在上面正在使用的简单递增计数器实际上并不要求加锁。上面的例子中更适合使用 AtomicInteger代替Integer作为计数器。

最后一点,无论你是否正在使用Plumber的自动死锁检测解决方案,还是手动从线程转储获得解决办法的信息,都希望这篇文章可以为你解决锁竞争的问题带来帮助。

感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!


# java  # 中锁的性能  # 中锁的性能如何提高  # 中锁  # 详解java中各类锁的机制  # java锁升级过程过程详解  # java synchronized 锁机制原理详解  # 关于Java锁性能提高(锁升级)机制的总结  # 数据结构  # 的是  # 死锁  # 加锁  # 而非  # 移到  # 解决办法  # 是在  # 会有  # 在这  # 几种  # 在上面  # 这篇文章  # 那段  # 先前  # 自己的  # 的人  # 而不是  # 情况下  # 事实上 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571


相关推荐: 晋江文学城电脑版官网 晋江文学城网页版直接进入  Laravel如何处理文件上传_Laravel Storage门面实现文件存储与管理  Laravel如何实现数据导出到CSV文件_Laravel原生流式输出大数据量CSV【方案】  C++时间戳转换成日期时间的步骤和示例代码  UC浏览器如何设置启动页 UC浏览器启动页设置方法  香港服务器租用每月最低只需15元?  利用python获取某年中每个月的第一天和最后一天  中山网站制作网页,中山新生登记系统登记流程?  Laravel如何发送系统通知?(Notification渠道示例)  Claude怎样写约束型提示词_Claude约束提示词写法【教程】  中山网站推广排名,中山信息港登录入口?  b2c电商网站制作流程,b2c水平综合的电商平台?  logo在线制作免费网站在线制作好吗,DW网页制作时,如何在网页标题前加上logo?  如何在云虚拟主机上快速搭建个人网站?  香港服务器网站生成指南:免费资源整合与高速稳定配置方案  微信小程序制作网站有哪些,微信小程序需要做网站吗?  绝密ChatGPT指令:手把手教你生成HR无法拒绝的求职信  Laravel路由怎么定义_Laravel核心路由系统完全入门指南  如何彻底删除建站之星生成的Banner?  详解vue.js组件化开发实践  如何在阿里云部署织梦网站?  如何快速搭建高效可靠的建站解决方案?  Laravel如何配置Horizon来管理队列?(安装和使用)  laravel怎么为应用开启和关闭维护模式_laravel应用维护模式开启与关闭方法  zabbix利用python脚本发送报警邮件的方法  如何用腾讯建站主机快速创建免费网站?  Laravel的.env文件有什么用_Laravel环境变量配置与管理详解  网站制作价目表怎么做,珍爱网婚介费用多少?  如何自定义建站之星网站的导航菜单样式?  Laravel 419 page expired怎么解决_Laravel CSRF令牌过期处理  如何在万网自助建站平台快速创建网站?  Chrome浏览器标签页分组怎么用_谷歌浏览器整理标签页技巧【效率】  网站建设保证美观性,需要考虑的几点问题!  猎豹浏览器开发者工具怎么打开 猎豹浏览器F12调试工具使用【前端必备】  个人网站制作流程图片大全,个人网站如何注销?  如何为不同团队 ID 动态生成多个非值班状态按钮  如何在 React 中条件性地遍历数组并渲染元素  如何制作一个表白网站视频,关于勇敢表白的小标题?  微信小程序 wx.uploadFile无法上传解决办法  教学论文网站制作软件有哪些,写论文用什么软件 ?  怎么用AI帮你设计一套个性化的手机App图标?  微信小程序 canvas开发实例及注意事项  ,怎么在广州志愿者网站注册?  Laravel数据库迁移怎么用_Laravel Migration管理数据库结构的正确姿势  如何自定义safari浏览器工具栏?个性化设置safari浏览器界面教程【技巧】  Python正则表达式进阶教程_复杂匹配与分组替换解析  邀请函制作网站有哪些,有没有做年会邀请函的网站啊?在线制作,模板很多的那种?  如何快速搭建自助建站会员专属系统?  Laravel如何使用Socialite实现第三方登录?(微信/GitHub示例)  HTML透明颜色代码在Angular里怎么设置_Angular透明颜色使用指南【详解】