Javaロックのパフォーマンスの向上


2ヶ月前、Plumbrに関する「検出スレッドロック」を紹介した後、私たちはすでに「hey、大牛、今私は何が私の性能に影響を与えるか知っていますが、今私はどうすればいいですか?」と受け取り始めました.こんな似たような疑問.
EMCの製品でソリューションの説明を構築することに力を入れていますが、この記事では、ロックを検出するために独立したツールを適用できるいくつかの一般的なテクニックを共有します.これらのテクニックには、ロック分割、同時データ構造、コードの代わりにデータを保護し、ロック範囲を低減することが含まれます.
鍵は悪魔ではありませんが、鍵の競争は.
スレッドコードを使用してパフォーマンスに問題が発生するたびに、ロックのせいにします.結局、ロックは遅いと拡張性が悪いと一般的に考えられています.そう考えると、コードの最適化が始まり、可能な限りロックが解除され、最終的には深刻な同時バグが発生します.
従って,競合ロックと非競合ロックの違いを理解することが重要である.1つのスレッドが同期ブロック/四角髪に入ることを試みている場合、別のスレッドも駆動されて入るとロック競合が発生する.このとき、2番目のスレッドは、1番目のスレッドが同期ブロックを完全に実行し、監視オブジェクトを解放するまで強制的に待つ必要がある.同期ブロックにアクセスしようとするスレッドが1つしかない場合、ロックは競合しません.
実際には、競合がない場合には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人の収容量を超えてはいけないことを保証しなければなりません.
しかし、このような解決策はいつでもテーブルの上のプレイヤーの席に責任を負います.--中程度のトラフィックのトランプサイトシステムでも、スレッドがロックを待つために解放されることは、ロック競合イベントを絶えずトリガーすることに決まっている.このロックブロックには口座残高とテーブル数の制限が含まれており、この2つの大きなオーバーヘッドの操作はロック競争の可能性と時間を増加させる.
ソリューションの最初のステップは、同期コードをメソッド宣言からメソッドに移動するのではなく、データを保護することです.上の簡単な例は、最初はあまり変わっていません.しかし、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 */}
}

最初は目立たない変化であったが,今ではクラス全体の挙動に影響を及ぼす.プレイヤーがゲームに参加するたびに、以前の同期方法のロック対象はGamerServerインスタンスであり、プレイヤーがテーブルを離れようとすると競合イベントを引き起こす.ロックをメソッド署名からメソッド内に移動すると、ロック競合の可能性が遅延し、減少します.
ロック範囲の縮小
次に、データが保護されていることを確認した後、ロック保護のコードが必要であることを確認する必要があります.例えば、上のコードが以下のように書き換えられた後
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倍小さい.
同時データ構造で
もう1つの改良は、従来の単一スレッドデータ構造を削除し、特定の設計のデータ構造で同時使用することである.たとえば、すべてのトランプテーブルを格納するために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はこの問題には役に立たない.しかし、ConcurrentHashMapにとって同時であり、テーブルの数を増やしたり減らしたりすることができるテーブルをcreateTable()およびdestroyTable()メソッドで作成および破棄することができます.
その他のヒントとテクニック
  • ロックの可視性を低減します.上記の例では、ロックの宣言はpublicなので、すべてのクラスが表示されます.そのため、監視対象をロックすることで、個別のクラスがあなたの運転を破壊する可能性があります.
  • javaを見てください.util.concurrent.locksは、ロック戦略の実施がソリューションを改善するかどうかを見てみましょう.
  • は原子的に操作される.上記の例では、単純なカウンタのインクリメントにはロックは必要ありません.カウントトラッキングでIntegerの代わりにAtomicIntegerを使用するのは、この例では適切である.

  • Plumbrを使用してロックソリューションを自動的にチェックしたり、スレッドダンプ中に手動で情報を抽出したりしても、この文章がロック競合の問題を解決するのに役立つことを望んでいます.
    テキストリンク:http://www.javacodegeeks.com/2015/01/improving-lock-performance-in-java.html