【Javaスレッド】スレッドセキュリティの3つの要素:原子性、可視性、秩序性


目次
  • 定義
  • 原子性
  • AtomicXxx
  • AtomicStampedReference

  • 可視性
  • 秩序性
  • 参考資料
  • 定義#テイギ#
    まず、スレッドセキュリティとは何かを考えてみましょうか??「Java同時プログラミング実戦」では、複数のスレッドがクラスにアクセスすると、実行環境がどのようなスケジューリング方式を採用しているか、またはこれらのスレッドがどのように交互に実行されるかにかかわらず、呼び出しコードに追加の同期が必要なく、このクラスが正しい動作を示す場合、このクラスはスレッドが安全であると定義されています.
    スレッドのセキュリティについては、主に原子性、秩序性、可視性のいくつかの面から出発します.
    原子性:反発アクセスを提供し、同じ時点でデータを操作できるスレッドは1つしかありません.例えば、atomicXXXクラス、synchronizedキーワードの応用.
    秩序性:1つのスレッドは他のスレッドの命令実行順序を観察し、命令が並べ替えられるため、この観察結果は一般的に乱雑である.例えばhappens-beforeの原則.
    ≪可視性|Visibility|emdw≫:プライマリ・メモリに対するスレッドの変更は、他のスレッドによってタイムリーに表示されます.例:synchronized,volatile.
    げんしせい
    AtomicXxx
    原子性といえば、よく知られているAtomicバッグから離れられないに違いない.JDKにはatomic類、AtomicInteger、AtomicLong、AtomicBooleanなどがたくさん提供されている.
    AtomicIntegerの例:
    
    class AtomicIntegerExample {
         
        private static final Logger log = LoggerFactory.getLogger(AtomicIntegerExample.class);
        //     
        public static int requestTotal = 500;
        //         
        public static int threadTotal = 20;
    
        public static AtomicInteger count = new AtomicInteger(0);
    
        public static void main(String[] args) throws Exception {
         
            ExecutorService executorService = Executors.newCachedThreadPool();//     
            final Semaphore semaphore = new Semaphore(threadTotal);//     
            final CountDownLatch countDownLatch = new CountDownLatch(requestTotal);
            for (int i = 0; i < requestTotal ; i++) {
         
                executorService.execute(() -> {
         
                    try {
         
                        semaphore.acquire();
                        add();
                        semaphore.release();
                    } catch (Exception e) {
         
                        log.error("exception", e);
                    }
                    countDownLatch.countDown();
                });
            }
            countDownLatch.await();
            executorService.shutdown();
            log.info("count:{}", count.get());
        }
    
        private static void add() {
         
            count.incrementAndGet();
        }
    }
    
    

    このDemoについて、debugeしてみて、下層部がどのように実現しているかを見てみましょう???主な方法:incrementAndGet()
    	/**
         * Atomically increments by one the current value.
         *
         * @return the updated value
         */
        public final int incrementAndGet() {
         
            return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
        }
    

    AtomicIntegerのincrementAndGetメソッドは、メモリ内の値を更新するためにスピン(ループ検出更新)を使用し、更新操作が原子操作であることを保証する楽観的ロックの実装である.
    スピンロックメカニズムを使用すると、どのような問題が発生しますか??長時間のスピンが失敗すると、CPUに非常に大きな実行コストがかかります.
    これに伴い、getAndAddIntメソッドである魔法系UnSafeについて、后期小编でも时间を割いて文章をまとめていきますので、楽しみにしていてください.
      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;
       }
    

    この方法のコード構造:do-while()を分析してから、実行ロジックを理解します.
    まずgetIntVolatile()メソッドを呼び出し、オブジェクトの参照と値のオフセット量を使用して現在の値を取得し、compareAndSwapInt検出を呼び出しobj内のvalueとexpectが等しい場合、他のスレッドがこの変数を変更したことがないことを証明すると、updateとして更新され、このステップのCASが成功しなければ、スピンでCAS操作を継続します.
    上記の方法のパラメータについては、特に説明する必要があります.そうしないと、本当に愚かになります.compareAndSwapInt()が達成したい目標はvar 1オブジェクトについて、現在の値var 2と下位の値var 5が等しい場合、それを後の値(var 5+var 4)に更新します.
    もっと重要なのは編集者が間違っていることを理解しないでください.もし問題があれば、大物の私信の不当なところをタイムリーに修正してほしいです.
    原子性下層実現の核心思想はCASであるが,CASにはABA問題がある.
    compareAndSetは、まず、現在の参照が所望の参照に等しいかどうかを確認し、現在のフラグが所望のフラグに等しいかどうかを確認し、すべてが等しい場合、その参照とそのフラグの値を所定の更新値に原子的に設定する.
    ABAとは何ですか???一つの値がAでBになってAになった場合、CASを使って検査したところ、その値は変わっていませんが、実際には変わっています.これがCASのABA問題です.
    ではABA問題に対して、皆さんはどのように解決したいと思っていますか??データベース内の楽観的なロックメカニズム、バージョン番号を考えることができます.だからJDKはAtomicStampedReferenceを引き出した...
    AtomicStampedReference
    まずこの類の方法を見て、みんなは注釈を翻訳することに注意して、各パラメータの意味を理解します
    /**
         * Atomically sets the value of both the reference and stamp
         * to the given update values if the
         * current reference is {@code ==} to the expected reference
         * and the current stamp is equal to the expected stamp.
         *
         * @param expectedReference the expected value of the reference
         * @param newReference the new value for the reference
         * @param expectedStamp the expected value of the stamp
         * @param newStamp the new value for the stamp
         * @return {@code true} if successful
         */
        public boolean compareAndSet(V   expectedReference,
                                     V   newReference,
                                     int expectedStamp,
                                     int newStamp) {
         
            Pair<V> current = pair;
            return
                expectedReference == current.reference &&
                expectedStamp == current.stamp &&
                ((newReference == current.reference &&
                  newStamp == current.stamp) ||
                 casPair(current, Pair.of(newReference, newStamp)));
        }
    
    

    この方法では、現在の参照が予想参照に等しく、現在のフラグが予想フラグに等しいかどうかを確認します.すべてが等しい場合は、参照とフラグの値を所定の更新値に原子的に設定します.
    可視性
    スレッド間の可視性とは何ですか?1つのスレッドの共有変数値の変更は、他のスレッドにタイムリーに表示されます.
    共有変数とは?1つの変数が複数のスレッドのワークメモリにコピーされている場合、この変数はこれらのスレッドの共有変数です.
    Javaメモリモデルとは?(Java Memory Model、略称JMM)
    JMMはjavaプログラムにおける各種変数(スレッド共有変数)へのアクセスルール、およびJVMにおける変数のメモリへの格納、およびメモリからの変数の読み出しといった下位レベルの詳細について説明する.ルール1:1>すべての変数はメインメモリに格納される2>各スレッドには独自のワークメモリがあり、スレッドが使用する変数のコピーが保存される(メインメモリの変数のコピー)
    ルール2:1>スレッド共有変数のすべての操作は、自分のワークメモリで行う必要があります.メインメモリから直接読み書きすることはできません.2>異なるスレッドの間で他のスレッドワークメモリの変数に直接アクセスすることはできません.スレッド間変数の伝達はメインメモリで行う必要があります.スレッドの可視性の詳細については、Volatileベースのアプリケーションを参照してください.私の別の記事「Javaスレッド」に移動して、Volatileキーワードと使用を深く理解してください.
    秩序性
    秩序性とは,プログラムが実行されるとき,プログラムのコード実行順序と文の順序が一致することである.なぜ一致しないのでしょうか?—並べ替え
    Javaメモリモデルでは、コンパイラとプロセッサが命令を再ソートできますが、再ソートプロセスは単一スレッドプログラムの実行には影響しませんが、マルチスレッド同時実行の正確性に影響します.秩序性については、周志明の「Java仮想マシンを深く理解する」という本で、秩序性を紹介した.Happends-Beforeの原則だ.
    1.       :     ,      ,                     ;
    2.    :  unLock              lock  ;
    3.volatile    :                         ;
    4.    :    A       B,   B        C,       A       C;
    5.      :Thread   start()                 ;
    6.      :   interrupt()                            ;
    7.      :                     ,      Thread.join()    、Thread.isAlive()                 ;
    8.      :                 finalize()     ;
    

    スレッドの可視性と秩序性の理解には、Javaメモリモデルを構築した上で理解と思考が必要ですが、理解すると抽象的ですが、シリーズの文章を読むたびに、異なる知識点を収穫することができます.努力しているプログラマー一人一人に敬意を表します!!!
    参考資料
    『Java高同時プログラミング実戦』『Java同時プログラミング』マルチスレッドセキュリティとJavaにおけるロック