Java volatileの使い方、原理

6254 ワード

マルチスレッドで使用し、変数を同期します.スレッドは効率を向上させるために、Aなどのメンバー変数をBなどのようにコピーし、スレッド内のAへのアクセスはBです.AとBの同期は、いくつかのアクションでのみ行われます.したがって、AとBが一致しない場合があります.volatileは、このような状況を回避するために使用されます.volatileは、修飾された変数がコピーを保持せず、メインメモリ内の(つまり、上記のA)に直接アクセスすることをjvmに伝えます.
Javaメモリモデルにはmain memoryがあり、各スレッドにも独自のmemory(レジスタなど)があります.パフォーマンスのために、スレッドは自分のmemoryでアクセスする変数のコピーを保持します.これにより、ある瞬間、あるスレッドのmemoryの値が別のスレッドのmemoryの値、またはmain memoryの値と一致しない場合があります.
1つの変数がvolatileとして宣言されると、この変数はいつでも他のスレッドによって変更されるため、スレッドmemoryにcacheすることはできません.次の例ではvolatileの役割を示します.
public class StoppableTask extends Thread {  
  
  private volatile boolean pleaseStop;  
  
  
  public void run() {  
  
    while (!pleaseStop) {  
  
     // do some stuff...  
  
    }  
  
 }  
  
  
  public void tellMeToStop() {  
  
   pleaseStop = true;  
  
  }  
  
}  

pleaseStopがvolatileとして宣言されておらず、スレッドがrunを実行するときに自分のコピーをチェックしている場合、他のスレッドがtellMeToStop()を呼び出してpleaseStopの値を変更したことをタイムリーに知ることはできません.
Volatileは一般的にsychronizedの代わりに使用できない.volatileは操作の原子性を保証できないため、i++だけであっても、実際には複数の原子操作から構成される:read i;inc; write iは、複数のスレッドがi++を同時に実行する場合、volatileが動作するiが同じメモリであることを保証するしかありませんが、汚いデータが書き込まれる可能性があります.Java 5に追加されたatomic wrapper classesに合わせると、それらのincreaseなどの操作にsychronizedは必要ありません.
Reference: http://www.javamex.com/tutorials/synchronization_volatile.shtml http://www.javamex.com/tutorials/synchronization_volatile_java_5.shtml http://www.ibm.com/developerworks/cn/java/j-jtp06197.htmlおそらくvolatileとsynchronizedの違いを比較すると最も説明しやすい.volatileは変数修飾子であり、synchronizedはコードまたは方法に作用する.次の3つのgetコードを見てください.
int i1;               
int geti1() {return i1;}   
volatile int i2;   
int geti2()  
{return i2;}   
int i3;                
synchronized int geti3() {return i3;}   
  geti1()  

現在のスレッドに格納されているi 1の数値が得られる.複数のスレッドには複数のi 1変数コピーがあり、これらのi 1間は互いに異なることができる.すなわち、別のスレッドがスレッド内のi 1値を変更した可能性があり、この値は現在のスレッド内のi 1値と異なることができる.実際、Javaには「メイン」メモリ領域という考えがあり、変数の現在の「正確な値」が格納されています.各スレッドには独自の変数コピーがありますが、この変数コピー値はプライマリメモリ領域に格納されているものとは異なります.したがって、「プライマリ」メモリ領域のi 1値は1であり、スレッド1のi 1値は2であり、スレッド2のi 1値は3である.これは、スレッド1とスレッド2がそれぞれのi 1値を変更し、この変更がプライマリメモリ領域または他のスレッドに伝達されない場合に発生する.geti 2()は、「プライマリ」メモリ領域のi 2数値を得る.volatileで修飾された変数には、プライマリメモリ領域とは異なる変数コピーは許可されません.すなわち、1つの変数がvolatileで修飾された後、すべてのスレッドで同期されなければならない.任意のスレッドで値が変更されると、他のすべてのスレッドはすぐに同じ値を取得します.当然、volatile修飾変数アクセスは、スレッドが独自の変数コピーを持っているため、一般的な変数よりも多くのリソースを消費します.volatileキーワードがスレッド間のデータ同期を実現した以上、synchronizedは何をしますか?ほほほ、それらの間には2つの違いがあります.まずsynchronizedはモニタを取得して解放します.2つのスレッドが同じオブジェクトロックを使用している場合、モニタはコードブロックが同時に1つのスレッドだけで実行されることを強制的に保証することができます.これは周知の事実です.ただし、synchronizedもメモリを同期します.実際、synchronizedは「プライマリ」メモリ領域でスレッド全体のメモリを同期します.そこでgeti 3()メソッドを実行するには、次の手順に従います.
  • スレッド要求thisオブジェクトを監視するオブジェクトロックを取得する(ロックされていないと仮定し、そうでなければロックが解放されるまでスレッドが待つ)
  • .
  • スレッドメモリのデータが消去され、「メイン」メモリ領域から読み込まれる(Java仮想機能最適化このステップ..[後の表現が分からない、汗])
  • コードブロックは、
  • を実行する.
  • 変数に対する任意の変更は、「プライマリ」メモリ領域に安全に書き込むことができる(ただしgeti 3()メソッドは変数値を変更しない)
  • .
  • スレッドはthisオブジェクトを監視するオブジェクトロックを解放するので、volatileはスレッドメモリとプライマリメモリの間で変数の値を同期するだけで、synchronizedはモニタをロックおよびロック解除することによってすべての変数の値を同期します.synchronizedはvolatileよりも多くのリソースを消費していることは明らかです.volatileキーワードはJavaマルチスレッドを知っている読者がその役割をよく知っていると信じています.volatileキーワードは、int、float、booleanなどの単純なタイプの変数を宣言するために使用されます.これらの単純なデータ型がvolatileとして宣言されると、それらの操作は原子レベルになります.しかし、これは一定の制限があります.例えば、次の例のnは原子レベルではない:
  • package  mythread;  
      
    public   class  JoinThread  extends  Thread  
    {  
         public   static volatile int  n  =   0 ;  
        public   void  run()  
        {  
             for  ( int  i  =   0 ; i  <   10 ; i ++ )  
                 try   
            {  
                    n  =  n  +   1 ;  
                    sleep( 3 );  //            ,  3     
      
                }  
                 catch  (Exception e)  
                {  
                }  
        }  
      
         public   static   void  main(String[] args)  throws  Exception  
        {  
      
            Thread threads[]  =   new  Thread[ 100 ];  
             for  ( int  i  =   0 ; i  <  threads.length; i ++ )  
                 //    100      
                threads[i]  =   new  JoinThread();  
             for  ( int  i  =   0 ; i  <  threads.length; i ++ )  
                 //         100      
                threads[i].start();  
             for  ( int  i  =   0 ; i  <  threads.length; i ++ )  
                 //  100             
                threads[i].join();  
            System.out.println( " n= "   +  JoinThread.n);  
        }  
    }   
    

    nに対する動作が原子レベルである場合、最終出力の結果はn=1000であるべきであり、上面積コードを実行する場合、多くの場合、出力されるnは1000未満であり、これはn=n+1が原子レベルの動作ではないことを示す.なぜなら、volatileとして宣言された単純な変数が、現在の値がその変数の以前の値によって関連付けられている場合、volatileキーワードは機能しません.つまり、次の式は原子操作ではありません.
    n  =  n  +   1 ; 
    n ++ ; 
    

    このような状況を原子操作にするにはsynchronizedキーワードを使用する必要があります.上記のコードは次のような形式に変更できます.
    package  mythread;  
      
    public   class  JoinThread  extends  Thread  
    {  
         public   static int  n  =   0 ;  
      
         public static   synchronized   void  inc()  
        {  
            n ++ ;  
        }  
         public   void  run()  
        {  
             for  ( int  i  =   0 ; i  <   10 ; i ++ )  
                 try   
                {  
                    inc();  //  n = n + 1     inc();   
                    sleep( 3 );  //            ,  3     
      
                }  
                 catch  (Exception e)  
                {  
                }  
        }  
      
         public   static   void  main(String[] args)  throws  Exception  
        {  
      
            Thread threads[]  =   new  Thread[ 100 ];  
             for  ( int  i  =   0 ; i  <  threads.length; i ++ )  
                 //    100      
                threads[i]  =   new  JoinThread();  
             for  ( int  i  =   0 ; i  <  threads.length; i ++ )  
                 //         100      
                threads[i].start();  
             for  ( int  i  =   0 ; i  <  threads.length; i ++ )  
                 //  100             
                threads[i].join();  
            System.out.println( " n= "   +  JoinThread.n);  
        }  
    }   
    

    上記のコードはn=n+1をinc()に変更し、incメソッドはsynchronizedキーワードを使用してメソッド同期を行う.したがって、volatileキーワードを使用する場合は慎重に、単純なタイプの変数がvolatile修飾を使用する限り、この変数のすべての操作が元の操作であるわけではありません.変数の値が自分の前の値によって決定された場合、n=n+1、n++など、volatileキーワードは失効し、変数の値が自分の前の値とは関係ない場合にのみその変数の操作が原子レベルである場合、n=m+1のように、これは元のレベルです.だからvolatileを使う肝心な時必ず慎重で、もし自分で自信がなければsynchronizedを使ってvolatileの代わりに使うことができます.