スレッドの安全性の問題の条件と解決策


1. スレッドセーフの条件

■ スレッド セーフティの問題の概念:

複数のスレッドが同時に実行され、共有データに対して非アトミックな操作が実行されるため実行結果が不一致になります

  • スレッド セーフの前提条件:複数のスレッドの存在同時実行(スレッドがリソースをめぐって競合状態にある)、非アトミック操作、および共有データ
  • スレッドの安全性が低い場合、次のような結果が生じます。数据不一致

スレッドセーフな結果: データは一貫しています; スレッドアンセーフな結果: データは一貫していません

スレッドの安全性の問題: スレッドの安全性の低下によって引き起こされる問題


■ 並行性、並列性

  • 同時実行性 (複数のスレッドが同じリソースを操作する)

    • 1 つの CPU コアが複数のスレッドをシミュレートし、CPU が高速に切り替わり、複数のスレッドが競合関係にあり、リソースを奪い合います。
  • 同時実行 (複数人が同時に開始)

    • CPUマルチコア、複数のスレッドを同時に実行可能、スレッドプール



2. スレッドセーフを解決する方法

☺ 解決策: 生成された 4 つの条件を解読するだけです

たとえば、複数のスレッドがある場合 ==> 単一スレッドを使用します

たとえば、同時実行 ==>同期、ロック(Synchronized、lock の使用など)、楽観的な同時実行戦略(CAS など)

たとえば、非アトミック操作 ==>アトミック操作(AtomicInteger などのアトミック操作クラス Atomic の使用など)

たとえば、共有データ ==> 変更されていないデータ型と既知のスレッド セーフを持つ変数 (Vector コレクション、アトミック クラス Atomic)、スレッド ローカル変数(ThreadLocal)

  • 同期とは、複数のスレッドが共有データに同時にアクセスするときに、その共有データが同時に 1 つのスレッドによってのみ使用されるようにすることを指します。



■ 一般的なスレッドセーフ ソリューション

(1) ロック方法

  • ReentrantLock は、 synchronized キーワードsynchronizedまたはlock のサブクラスを使用して再入可能にできます。
  • 相互排他的同期が直面する主な問題は、スレッドのブロックとウェイクアップによって生じるパフォーマンスのオーバーヘッドであるため、この種の同期はブロッキング同期とも呼ばれます。[悲観的な同時実行戦略]
  • 同時実行性の高いシナリオでは、 Lock ロックを使用することをお勧めします。Lock ロックを使用すると、Synchronize キーワードを使用するよりもパフォーマンスが大幅に向上します。さらにLock锁底层就是通过AQS+CAS机制实现的、CAS はスレッド セーフを解決するもう 1 つの方法でもあります。

★ロックとシンクロロックの違いについての話

  • synchronized は关键字C++ を使用して実装されており、ロックの開始と終了を制御する方法はなく、スレッドの実行を中断する方法もありません。

  • lock を使用するとjava层面的实现設定を通じてロックのステータスを取得したり、ロックを開いたり、ロックを解放したり、スレッドの実行を中断したりできるため、より柔軟になります。

  • 自動的にロックするかどうか: synchronized は自動的にロックするかどうかを指定します。ロックは、unlock メソッドを呼び出して手動で解放する必要があります。そうしないと、無限ループになります。

lock.lock();//其他没有拿到锁的线程?阻塞 卡着不动
boolean res = lock.tryLock(1000, TimeUnit.MILLISECONDS);//一秒之后如果没有拿到锁,就返回false

lock.lockInterruptibly();//中断方法

(2) 楽観的な同時実行戦略

  • CAS オプティミスティック同時実行戦略、ロックフリー メカニズム

  • 基礎となるスレッドセーフなアトミック操作クラス Atomic は CAS アイデアを使用しますが、その実装はUnsafe の CPU プリミティブ レベルのアセンブリ操作に依存しています。

    new AtomicInteger().getAndAdd(1);//获取到当前值并加1
    // 底层实现
    public final int getAndAdd(int delta) {
          
          
       return unsafe.getAndAddInt(this, valueOffset, delta);
    }
    public final int getAndAddInt(Object var1, long var2, int var4) {
          
          
       int var5;
       do {
          
          
          var5 = this.getIntVolatile(var1, var2);
       } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
       return var5;
    }
    

    職場では、Unsafe クラスを使用することはお勧めできません。クラス名を見ると「危険」であることがわかります。

    • 競合検出に基づく楽観的な同時実行戦略は、平たく言えば、リスクに関係なく最初に操作を実行することです。他のスレッド共有データをめぐって競合しない場合、操作は直接成功します。共有データが実際に競合していて競合している場合は、操作は直接成功します。他の補償手段を実行します。最も一般的に使用される補償手段は、共有データの競合がなくなるまで継続的に再試行することです。
    • オプティミスティック同時実行戦略を使用するには、「ハードウェア命令セットの進化」が必要ですか? ** ** を要求しなければならないからです操作和冲突检测这两个步骤具备原子性原子性を確保するにはどうすればよいでしょうか? ==>cpu 指令
  • CAS で考えられる問題: 無限ループ、ABA 問題

    • ABA 問題の解決策: 原子引用类 AtomicStampedReference==> は「オプティミスティック ロックとバージョン番号」に似ています。

(3) スレッドプライベートローカル変数

  • threadLocal、スレッドプライベートローカル変数などのスレッドローカルストレージは、共有変数の競合を回避します。

(4) その他の方法

  • 使用 volatile、その可視性を使用して命令の並べ替えを禁止しますが、アトミック性は保証できません。マルチスレッドでは書き込み操作の競合に対する厳密な同期要件がないため、これをお勧めします。

    一般的なシナリオは、揮発性変数を使用してスレッドの終了を制御することです。

  • 書くことはコピーすること – CopyOnWriteArrayList

    CopyOnWriteArrayList は、JUC パッケージによって提供されるスレッドセーフなリストです。



3. スレッドの安全性の問題を解決するために一般的に使用される方法を要約する

  • ロック: 同期、ロック
  • (ロックフリー) オプティミスティック同時実行: CAS
  • アトミック操作クラス: アトミック
  • (スレッドローカル変数) 変数は共有されません: threadlocal
  • 揮発性の




この記事が役に立った場合は、忘れずに Yile に「いいね!」を押してください。ありがとうございます。

おすすめ

転載: blog.csdn.net/weixin_45630258/article/details/127035936
おすすめ