マルチスレッド同時実行の一般的な使用仕様の一部

目次

1. マルチスレッド同時使用仕様

1.1 スレッド名の指定

2. スレッドプールを使用してみる

3. 遺言執行者は許可されません

4. スレッドを適切に停止する

5. 停止可能なランナブルの作成

6. すべての例外は Runnable でキャッチされる必要があります

 7. ThreadLocal の使用を検討する

8. ショートニングロック 

9. 個別のロック、分散ロック、さらにはロックのないデータ構造を選択する

10. 推奨事項] ThreadLocal に基づくロックを避ける

11. デッドロックのリスクを回避する

12. volatile 修飾子と AtomicXX シリーズの正しい使用方法

13. 遅延初期化の正しい書き方


1. マルチスレッド同時使用仕様

1.1スレッド名の指定

[必須] スレッドまたはスレッド プールを作成するときは、エラー発生時のバックトラッキングを容易にするために、意味のあるスレッド名を指定してください。

  1. シングルスレッド作成時にスレッド名を直接指定する

      Thread thread = new Thread();
      thread.setName("a");

     2. スレッド プールは、guava または自己カプセル化された ThreadFactory を使用して命名規則を指定します。

2. スレッドプールを使用してみる

【推奨】スレッドプールを利用してスレッドを作成してみる

    特別な状況を除いて、スレッドのリソースを保護するために、自分でスレッドを作成しないようにしてください。 

    同様に、タイマーには Timer を使用せず、ScheduledExecutorService を使用する必要がありますタイマーにはスレッドが 1 つしかないため、タイマー内で定義された複数のタスクを同時に実行することはできません。また、タスクの 1 つが例外をスローすると、タイマー全体もハングアップし、ScheduledExecutorService には例外をキャッチしないタスクしかありません。その他のミッションは影響を受けません。 

3.遺言執行者は許可されません

[必須] リソース枯渇のリスクを避けるため、スレッド プールの作成にエグゼキュータを使用することはできません。

Executor によって返されるスレッド プール オブジェクトの欠点:

  1. 固定スレッドプールとシングルスレッドプール:

許可されるリクエスト キューの長さは Integer.MAX_VALUE ですが、これにより大量のリクエストが蓄積され、OOM が発生する可能性があります。

  1. キャッシュされたスレッド プールとスケジュールされたスレッド プール:

作成できるスレッドの数は Integer.MAX_VALUE であり、多数のスレッドが作成されて OOM が発生する可能性があります。

newThreadPoolExecutor(xxx,xxx,xxx,xxx) によりスレッドプールの動作ルールを明確にし、Queue とスレッドプールの coresize と maxsize を適切に設定する必要があり、vjkit でカプセル化された ThreadPoolBuilder を使用することを推奨します。 。

4. スレッドを適切に停止する

【必須】スレッドを適切に停止する

Thread.stop () は推奨されません。強制終了は危険すぎるため、不完全なロジックや非アトミックな操作が発生します。これは非推奨メソッドとして定義されています。

単一のスレッドを停止するには、Thread.interrupt() を実行します。

スレッド プールを停止します。

ExecutorService.shutdown():新しいタスクを送信したり、現在のタスクとキュー内のタスクが実行されるのを待ってから終了したりすることはできません。

ExecutorService.shutdownNow(): Thread.interrupt() を通じて実行中のすべてのスレッドを停止し、キュー内で待機しているタスクの処理を停止します。

最もエレガントな終了方法は、最初に shutdown() を実行し、次にvjkit の ThreadPoolUtil によってカプセル化されているshutdownNow()を実行することです。

Thread.interrupt() は、実行中のスレッドに中断できることを保証するものではないことに注意してください。中断および終了できる Runnable を作成する必要があります。ルール 5 を参照してください。

5. 停止可能なランナブルの作成

[必須] 停止可能な Runnable を作成する

Thread.interrupt() の実行時、スレッドが sleep()、wait()、join()、lock.lockInterruptibly() などのブロッキング状態にある場合、InterruptedException がスローされます。上記の状態では、スレッド状態は中断に設定されます。

したがって、次のコードはスレッドを中断できません。


     public void run(){
         while (true){
            sleep();
         }
      }
      public void sleep(){
         try {
            Thread.sleep(1000);
         } catch (InterruptedException e) {
            throw new RuntimeException(e);
         }
      }
  1. InterruptExceptionを適切に処理する

        InterruptException は処理する必要がある CheckedException であるため、run() によって呼び出されるサブ関数は例外を簡単に処理してログに処理することができますが、これは割り込みの送信を停止するのと同等であり、外部関数は割り込みを受け取りません。割り込み要求。元のサイクルを継続するか、次のブロックに入ります。

        正しい処理は、Thread.currentThread().interrupt(); を呼び出して割り込みを渡すことです。

  public void sleep(){
         try {
            ....
         } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
         }
      }
  1. メインループに進みブロッキング状態に入る前に、スレッドの状態を判断する必要があります

6. すべての例外は Runnable でキャッチされる必要があります

[必須] すべての例外は Runnable でキャッチされる必要があります

RuntimeException が Runnable でキャッチされずにスローされた場合、次のことが起こります。

  1. ScheduledExecutorService はスケジュールされたタスクを実行します。タスクは中断され、タスクは定期的にスケジュールされなくなりますが、スレッド プール内のスレッドは他のタスクにも使用できます。

  2. ExecutorService がタスクを実行すると、現在のスレッドが中断され、スレッド プールは後続のタスクに応答するために新しいスレッドを作成する必要があります。

  3. ThreadFactory でカスタム UncaughtExceptionHanlder を設定しない場合、例外は System.err にのみ出力され、プロジェクト ログには出力されません。

したがって、自作の Runnable では例外が確実にキャッチされるようにすることをお勧めします。サードパーティの Runnable の場合は、vjkit の SafeRunnable でラップできます。

 7. ThreadLocal の使用を検討する

[必須] グローバル非スレッドセーフ オブジェクトは ThreadLocal に保存できます

グローバル変数には、シングルトン オブジェクトと静的メンバー変数が含まれます。

よく知られている非スレッドセーフ クラスには、 SimpleDateFormat、MD5/SHA1 のダイジェストなどがあります

これらのクラスは使用するたびに作成する必要があります。

ただし、作成に一定のコストがかかる場合は、ThreadLocal を使用して保存し、再利用できます。

ThreadLocal 変数は静的変数として定義し、使用する前にリセットする必要があります。

8. ショートニングロック 

【おすすめ】ショートニングロック

  1. ブロックをロックできる場合は、メソッド本体全体をロックしないでください。

  1. オブジェクト ロックを使用できる場合は、クラス ロックを使用しないでください。

9. 個別のロック、分散ロック、さらにはロックのないデータ構造を選択する

[推奨事項] 個別のロック、分散ロック、またはロックのないデータ構造を選択する

  1. スプリットロック:

1) 読み取り/書き込み分離ロック ReentrantReadWriteLock、読み取りと読み取りの間のロックはなし、書き込みと書き込みの間のロックのみ。

2) ArrayBase のキューは通常、グローバル ロックですが、LinkedBase のキューは通常、キューの先頭と末尾の 2 つのロックです。

  1. 分散ロック (セグメント化ロックとも呼ばれます):

1) たとえば、JDK7 の ConcurrentHashMap は 16 個のロックに分割されます。

2) 少量の書き込みと読み取りが頻繁に行われるカウンタの場合、パフォーマンスを向上させるために、JDK8 または vjkit によってカプセル化された LongAdder オブジェクトを使用することをお勧めします (複数のカウンタへの内部分散、オプティミスティック ロックの使用の削減、およびすべてのカウンタの追加が必要な場合にすべてのカウンタを追加します)。値の取得)

  1. ロックフリーのデータ構造:

1) JDK8 の ConcurrentHashMap など、完全にロックフリー、待機フリーの構造。

2) CAS ベースのロックフリーおよび待機データ構造 (AtomicXXX シリーズなど)。

ThreadLocal に基づくロックを回避する

10. 推奨事項] ThreadLocal に基づくロックを避ける

たとえば、Random インスタンスはスレッドセーフですが、そのシード アクセスは実際にはロックによって保護されています。したがって、JDK7 の ThreadLocalRandom を使用して、各スレッドにシードを配置してロックを回避することをお勧めします。

11. デッドロックのリスクを回避する

【推奨事項】デッドロックリスクを回避する

複数のリソースと複数のオブジェクトをロックする順序は一貫している必要があります。

デッドロックを完全に回避することができない場合は、タイムアウト制御を備えた tryLock ステートメントを使用してロックできます。

12. volatile 修飾子と AtomicXX シリーズの正しい使用方法

【おすすめ】volatile修飾子、AtomicXXシリーズの正しい

複数のスレッドで共有されるオブジェクトの場合、単一スレッドでの変更がすべてのスレッドに表示されるとは限りません。volatile を使用して変数を定義すると解決できます (可視性が解決されます)。

ただし、同時実行 counter++ など、複数のスレッドが現在の値に基づいて同時に変更を行う場合、volatile は無力です (アトミック性を解決できません)。

この時点で、Atomic* シリーズを使用できます。

ただし、複数の AtomicXXX Counter を同時にアトミックに操作する必要がある場合は、変更されたコード ブロックをロックするために synchronized を使用する必要があります。

13. 遅延初期化の正しい書き方

【おすすめ】遅延初期化の正しい書き方

二重チェック ロックによる遅延初期化の実装には隠れた危険があります。ターゲット属性を volatile として宣言する必要があります。パフォーマンスを高めるには、一時変数に volatile 属性を割り当てる必要がありますが、これは複雑です。

したがって、単純に初期化を遅らせたい場合は、次の静的クラス メソッドを使用して JDK 独自のクラス ロード メカニズムを使用し、一意の初期化を確保できます。

おすすめ

転載: blog.csdn.net/XikYu/article/details/131247324