ノートを読む-Java並行プログラミングの戦闘-第2章スレッドセーフ

スレッドセーフティの問題の前提:

この変数は変更できます

変数は複数のスレッドによってアクセスされます

複数のスレッドが適切な同期なしに同じ変数の状態変数にアクセスすると、プログラムでエラーが発生します。この問題を解決するには3つの方法があります。

  • スレッド間で状態変数を共有しない
  • 状態変数を不変変数に変更する
  • 訪問時に同期を使用する

1.スレッドセーフとは

スレッドセーフ:複数のスレッドが特定のクラスにアクセスする場合、クラスは正しく動作し、このクラスをスレッドセーフと呼びます。

ステートレスオブジェクトはスレッドセーフでなければならない

(ステートフルとステートレス:ステートフルとは、データを保存する機能を持つクラスを意味します。ステートレスとは、クラスの属性にデータを保存しないことを意味します)

2.原子性

1. 競合状態:複数のスレッドが同じリソースにアクセスする場合、スレッドの実行順序が異なると結果に一貫性がなくなり、現時点で競合状態が発生します。複数のスレッドによってアクセスされるコードリソースは、クリティカルセクションと呼ばれます。

  • 最初に確認してから実行します。失敗する可能性のある観察に基づいて判断するか、特定の操作を実行します。この種の競合状態は、実行前のチェックと呼ばれます。

2. 遅延初期化の競合状態:遅延初期化は、メモリを節約する必要があるときにオブジェクトを初期化すると同時に、オブジェクトが1回だけ初期化されるようにする必要があります。ただし、マルチスレッドの場合、2つのスレッドが同時に初期化条件を実行し、1つ目のスレッドの初期化が完了していない場合、2つ目のスレッドがこの時点で条件判定を行います。その結果、オブジェクトは初期化されておらず、2番目のスレッドが初期化を再度実行します。現在、スレッドセーフティの問題が発生しています。1回実行されるべきコードが2つのスレッドによって1回実行されました。第1、第2スレッドの初期化が完了する前に、初期化条件を判断する別のスレッドがある場合。次に、もう1つの初期化が実行されます。これは私たちが見たくない状況です。

3. 複合操作:アトミックに実行する必要があるいくつかのアクションの組み合わせを複合操作と呼びます。たとえば、最初にチェックしてから実行すると、アトミックに実行される一連のアクションがあります。したがって、最初にチェックしてから実行すると、複合操作になります。スレッドの安全性を確保したい場合は、複合操作の各グループがアトミックに実行されるようにする必要があります。一般的に言えば、ロックによってこの目標を達成します。

3、ロック機構

1.組み込みロック: Javaは、原子性をサポートする組み込みロックメカニズムを提供します:同期されたコードブロック。同期コードブロックは2つの部分で構成されています。1つはロックのオブジェクト参照であり、もう1つはロックの保護コードブロックです。キーワードSynchronizedで変更されたメソッドは、メソッド本体全体を含む同期メソッドです。このコードブロックのロックは、このメソッドを呼び出すインスタンスです。それはこれです。同期変更が静的メソッドの場合、コードブロックのロックは現在のクラスの.CLASSオブジェクトです。

2.再入可能:別のスレッドが保持しているロックをスレッドが要求すると、要求側のスレッドはブロックされます。ただし、内部ロックは再入可能であるため、スレッドがすでに保持しているロックを取得しようとすると、要求は成功します。再入可能性を実現する1つの方法は、ロックをカウンターと所有者スレッドに関連付けることです。カウンターが0の場合、ロックはどのスレッドによっても保持されていません。このとき、ロックを適用するスレッドは成功できます。同時に、JVMはロックの所有者を記録し、カウンターを1に設定します。このロックを取得すると、カウンターが増加し、スレッドが同期コードブロックを終了すると、カウンターが0になり、ロックが解放されるまで、カウンターが減少します。

3.再入の利点:次のコードに示すように、サブクラスが親クラスの同期メソッドをオーバーライドする場合。LoggingWidgetサブクラスのdoSomethingを実行すると、スレッドはロックを取得します。サブクラスのdoSomethingが実行されると、親のdoSomethingが再度呼び出されます。組み込みロックが再入可能でない場合、親クラスのdoSomethingは永久に待機します。再入は、この種のデッドロックを回避します。

public class Widget{
    
    
    public synchronized void doSomething(){
    
    
        ...
    }
}

public class LoggingWidget extends Widget{
    
    
    public synchronized void doSomething(){
    
    
        system.out.println("=================");
        super.doSomething();
    }
}

4番目に、ロックを使用して状態を保護します

変数はロックによって保護できます。これにより、一度に1つのスレッドだけがこの変数を操作していることが保証されます。スレッドが変数の操作を完了した場合にのみ、他のスレッドは変数を操作できます。

5.アクティビティとパフォーマンス

  • ロックは実際にプログラムのセキュリティを保証します。パフォーマンスを向上させるためにマルチスレッドが使用されているため、セキュリティを考慮する必要があります。マルチスレッドの場合、スレッドの安全性の問題が発生するため、同期を確実にするためにロックする必要があります。しかし、これは安全のためにパフォーマンスを犠牲にしなければならないという意味ではありません。したがって、パフォーマンスを確保しながらプログラムのセキュリティを確保する必要があります。ブラインドセキュリティのパフォーマンスを犠牲にしないでください。マルチスレッド化を導入しても、意味がありません。
  • したがって、ロックするときは、同期コードブロックのサイズを適切に判断する必要があります。この時点で、セキュリティ、シンプルさ、パフォーマンスの間のトレードオフを見つける必要があります。単純さとパフォーマンスの間にはしばしば矛盾があります。

注:短時間で完了しない可能性がある長期的な計算または操作(ネットワークI / OまたはコンソールI / O)を実行する場合は、ロックを保持しないでください。

おすすめ

転載: blog.csdn.net/weixin_45373852/article/details/108726264