课程咨询 :13486356750 QQ:1079585464

厦门达内java培训

  • 怎样提高 Java 中锁的性能

    发布:厦门达内      来源:达内培训      时间:2015-11-16


  •     达内java培训专家发现,我们在多线程的代码中遇到性能方面的问题时,大多会认为是锁的问题,毕竟大家都知道锁会降低程序的运行速度和低扩展性,抱着这种想法,优化代码很容易出现并发问题。事实上,了解竞争锁和非竞争锁的不同是尤为重要的。

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

        在非竞争的情况下和大多数的应用中,JVM已经对同步进行了优化。非竞争锁在执行过程中不会带来任何额外的开销。因此,你应当担心的是锁的竞争。达内java培训专家给出下列方法,来降低竞争的可能性、减少竞争的持续时间。

        一、保护数据而非代码

        解决线程安全问题的一个快速的方法就是对整个方法的可访问性加锁。举个例子,通过这种方法来建立一个在线扑克游戏服务器:

    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
    }

        这样那段包含对玩家账号余额检测的可能引起费时操作的代码,被移到了锁控制的范围之外。现在锁仅仅被用来防止玩家人数超过桌子可容纳的人数,对账户余额的检查不再是该保护措施的一部分了。

        三、分离锁

        以上代码最后一段可以看到:整个数据结构是由相同的锁保护着。考虑到在这一种数据结构中可能会有数以千计的牌桌,而我们必须保护任何一张牌桌的人数不超过容量,在这样的情况下仍然会有很高的风险出现竞争事件。

        对此有个简单的办法,就是对每一张牌桌引入分离锁:

    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
    }

        现在,我们只对单一牌桌的可访问性进行同步而不是所有的牌桌,这样就显著降低了出现锁竞争的可能性。

        四、使用线程安全的数据结构

        另一个可以改善的地方就是抛弃传统的单线程数据结构,改用被明确设计为线程安全的数据结构。当采用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来说是完全同步的,其允许我们以并行的方式添加或减少牌桌的数量。

       

上一篇:Java8 流的特性及Lambda 表达式

下一篇:Java常量池解析与CONSTANT入口解析

最新开班日期  |  更多

Java--大数据周末班

Java--大数据周末班

开班日期:每周一

Java--大数据全日制班

Java--大数据全日制班

开班日期:每周一

Java--零基础周末班

Java--零基础周末班

开班日期:每周一

Java--零基础全日制班

Java--零基础全日制班

开班日期:每周一

  • 地址:厦门软件园二期望海路59号之一401达内科技
  • 课程培训电话:13486356750 QQ:1079585464     全国服务监督电话:400-111-8989
  • 服务邮箱 tousu@tedu.cn
  • 2001-2016 达内时代科技集团有限公司 版权所有 京ICP证8000853号-56