1.スレッドの安全性の問題はいつ発生しますか?
単一のスレッドではスレッドの安全性の問題はありませんが、マルチスレッドプログラミングでは、同じリソースに同時にアクセスすることができます。このリソースは、変数、オブジェクト、ファイル、データベーステーブルなど、さまざまなタイプのリソースを使用できます。複数のスレッドが同じリソースに同時にアクセスすると、問題があります。
各スレッドによって実行されるプロセスは制御できないため、最終結果は実際の希望に反しているか、プログラムエラーに直接つながる可能性があります。
簡単な例を見てみましょう:
これで、ネットワークからデータを個別に読み取り、データベーステーブルに挿入する2つのスレッドがあり、複製データを挿入できないことを要求します。
次に、データを挿入するプロセスで2つの操作が必要です。
1)データがデータベースに存在するかどうかを確認します。
2)存在する場合、挿入されません。存在しない場合は、データベースに挿入されます。
2つのスレッドがそれぞれスレッド1とスレッド2で表され、ある時点でスレッド1とスレッド2がデータXを読み取る場合、これは起こる可能性があります。
Thread-1はデータXがデータベースに存在するかどうかをチェックし、Thread-2はデータXがデータベースに存在するかどうかもチェックします。
その結果、2つのスレッドのチェックの結果は、データXがデータベースに存在しないため、両方のスレッドはデータXをそれぞれデータベーステーブルに挿入します。
これはスレッドの安全性の問題です。つまり、複数のスレッドが同時にリソースにアクセスする場合、プログラムの実行結果は表示したい結果ではありません。
ここでは、このリソースは次のように呼ばれます:Criticalリソース(共有リソースとも呼ばれます)。
つまり、複数のスレッドが重要なリソース(1つのオブジェクト、オブジェクト内の属性、ファイル、データベースなど)にアクセスすると、スレッドの安全性の問題が発生する場合があります。
ただし、複数のスレッドがメソッドを実行する場合、メソッド内のローカル変数は重要なリソースではありません。メソッドはスタックで実行され、Javaスタックはスレッドプライベートであるため、スレッドの安全性の問題はありません。
2。スレッドの安全性の問題を解決する方法は?
それでは、一般的に、スレッドの安全性の問題を解決する方法は?
基本的に、スレッドの安全性の問題を解決する場合、すべての同時性モードは、「クリティカルリソースへのシリアル化アクセス」ソリューションを採用します。つまり、同時に、同期相互に排他的なアクセスとしても知られる重要なリソースにもアクセスできるスレッドは1つだけです。
一般的に、重要なリソースにアクセスするコードの前にロックが追加されます。重要なリソースにアクセスした後、ロックがリリースされ、他のスレッドがアクセスし続けます。
Javaでは、同期Mutexアクセスを実装する2つの方法が提供されています:同期とロック。
この記事では、主に同期の使用について説明しており、ロックの使用について次のブログ投稿で説明します。
3.Synchronized同期方法または同期ブロック
同期されたキーワードの使用を理解する前に、まず、概念を見てみましょう:Mutexロック、名前が示すように:Mutexアクセスの目的を実現できるロック。
簡単な例を示すために、重要なリソースがミューテックスに追加された場合、1つのスレッドが重要なリソースにアクセスすると、他のスレッドは待つことしかできません。
Javaでは、各オブジェクトにはモニターとも呼ばれるロックマーク(モニター)があります。複数のスレッドがオブジェクトに同時にアクセスすると、スレッドはオブジェクトのロックを取得した場合にのみアクセスできます。
Javaでは、同期されたキーワードを使用して、メソッドまたはコードブロックをマークすることができます。スレッドがオブジェクトの同期メソッドを呼び出すか、同期コードブロックにアクセスすると、スレッドはオブジェクトのロックを取得します。他のスレッドは、当面の間、方法にアクセスできません。メソッドが実行されるか、コードブロックが実行された場合にのみ、スレッドはオブジェクトのロックを放出し、他のスレッドはメソッドまたはコードブロックを実行できます。
以下は、同期されたキーワードの使用を説明するための簡単な例です。
1。同期されたメソッド
次のコードでは、2つのスレッドがINSERTDATAオブジェクトを呼び出してデータを挿入します。
public class test {public static void main(string [] args){final InsertData insertData = new InsertData(); new shood(){public void run(){insertdata.insert(thread.currentthread()); }; }。始める(); new shood(){public void run(){insertdata.insert(thread.currentthread()); }; }。始める(); }} class insertData {private arraylist <integer> arraylist = new ArrayList <Integer>(); public void insert(thread thread){for(int i = 0; i <5; i ++){system.out.println(thread.getName()+"INSERT DATA"+i); arraylist.add(i); }}}この時点で、プログラムの出力結果は次のとおりです。
これは、2つのスレッドが挿入メソッドを同時に実行することを示しています。
挿入方法の前に同期されたキーワードが追加された場合、実行結果は次のとおりです。
class insertData {private arraylist <integer> arrayList = new ArrayList <Integer>(); public synchronized void insert(thread thread){for(int i = 0; i <5; i ++){system.out.println(thread.getName()+"insert data"+i); arraylist.add(i); }}}上記の出力から、スレッド1に挿入されたデータは、スレッド0が挿入された後にのみ実行されます。これは、Thread-0およびThread-1が挿入メソッドを順番に実行することを示しています。
これは同期された方法です。
ただし、注意すべき点がいくつかあります。
1)スレッドがオブジェクトの同期メソッドにアクセスしている場合、他のスレッドはオブジェクトの他の同期メソッドにアクセスできません。オブジェクトには1つのロックしかないため、この理由は非常に単純です。スレッドがオブジェクトのロックを取得すると、他のスレッドはオブジェクトのロックを取得できないため、オブジェクトの他の同期メソッドにアクセスできません。
2)スレッドがオブジェクトの同期メソッドにアクセスしている場合、他のスレッドはオブジェクトの非同期メソッドにアクセスできます。この理由は非常に簡単です。非同期メソッドにアクセスしても、オブジェクトのロックは必要ありません。同期されたキーワードでメソッドが変更されていない場合、重要なリソースを使用しないことを意味します。他のスレッドはこのメソッドにアクセスできます。
3)スレッドAがオブジェクトオブジェクト1の同期メソッドfun1にアクセスする必要があり、別のスレッドbがオブジェクト1とオブジェクト2が同じタイプであっても、オブジェクトオブジェクト2の同期メソッドfun1にアクセスする必要がある場合、異なるオブジェクトにアクセスするため、スレッド安全性の問題はないため、相互除外の問題はありません。
2。同期コードブロック
同期されたコードブロックは、次の形式に似ています。
同期(syncobject){
}
このコードブロックがスレッドで実行されると、スレッドはオブジェクトSynobjectのロックを取得し、他のスレッドが同時にコードのブロックにアクセスすることが不可能になります。
Synobjectは、現在のオブジェクトを取得するロックを表すか、クラスの属性であり、属性を取得するロックを表すことができます。
たとえば、上記の挿入方法は、次の2つのフォームに変更できます。
class insertData {private arraylist <integer> arrayList = new ArrayList <Integer>(); public void insert(thread thread){synchronized(this){for(int i = 0; i <100; i ++){system.out.println(thread.getName()+"insert data"+i); arraylist.add(i); }}}} class insertData {private arrayList <integer> arrayList = new ArrayList <Integer>();プライベートオブジェクトオブジェクト= new Object(); public void insert(thread thread){synchronized(object){for(int i = 0; i <100; i ++){system.out.println(thread.getName()+"insert data"+i); arraylist.add(i); }}}}上記からわかるように、同期されたコードブロックは、同期された方法よりもはるかに柔軟に使用できます。おそらく、メソッドのコードの一部のみを同期する必要があるため、メソッド全体が現時点で同期されている場合、プログラムの実行効率に影響します。この問題は、同期コードブロックを使用することで回避できます。同期コードブロックは、同期が必要な場合にのみ同期することができます。
さらに、各クラスにはロックがあり、静的データメンバーへの同時アクセスを制御するために使用できます。
また、スレッドがオブジェクトの非静的同期方法を実行し、別のスレッドがオブジェクトが属するクラスの静的同期方法を実行する必要がある場合、この時点では、静的同期メソッドにアクセスすると、非静的同期メソッドがオブジェクトを占めるため、クラスロックにアクセスするため、この時点で相互排除はありません。
次のコードを見ることで理解できます。
public class test {public static void main(string [] args){final InsertData insertData = new InsertData(); new shood(){@override public void run(){insertdata.insert(); } }。始める(); new shood(){@override public void run(){insertdata.insert1(); } }。始める(); }} class insertData {public synchronized void insert(){system.out.println( "execute insert"); try {thread.sleep(5000); } catch(arturnedexception e){e.printstacktrace(); } system.out.println( "execute insert1"); } public synchronized static void insert1(){system.out.println( "execute insert1"); system.out.println( "execute insert1"); }}実行結果;
挿入メソッドは最初のスレッドで実行されます。これにより、2番目のスレッドがINSERT1メソッドをブロックしません。
同期されたキーワードが何をするかを見てみましょう。バイトコードを逆コンパイルしましょう。次のコードの分解されたバイトコードは次のとおりです。
public class insertData {private object = new object(); public void insert(thread thread){synchronized(object){}} public synchronized void insert1(thread thread){} public void insert2(thread thread){}}分解によって取得されたバイトコードから、同期されたコードブロックには実際にMonitorEnterとMonitorexitの2つの命令があることがわかります。 MonitorEnter命令が実行されると、オブジェクトのロックカウントが1増加し、Monitorexit命令が実行されると、オブジェクトのロック数が1削減されます。実際、これはオペレーティングシステムのPV操作に非常に似ています。オペレーティングシステムのPV操作は、複数のスレッドを制御して重要なリソースにアクセスするために使用されます。同期されたメソッドの場合、実行中のスレッドは、メソッドのmethod_info構造がacc_synchronizedフラグ設定を持っているかどうかを認識し、オブジェクトのロックを自動的に取得し、メソッドを呼び出し、最後にロックを解放します。例外が発生した場合、スレッドは自動的にロックを解放します。
注意すべきことの1つは、同期されたメソッドまたは同期コードブロックの場合、例外が発生した場合、JVMは現在のスレッドで占められているロックを自動的にリリースするため、例外のためにデッドロックはありません。
3。同期した他のいくつかの注目すべきこと
1.同期化された同期と静的同期の違い
同期されたロッククラスの現在のインスタンスは、他のスレッドが同時にクラスのインスタンスのすべての同期ブロックにアクセスしないようにします。これは「クラスの現在のインスタンス」であり、クラスの2つの異なるインスタンスにそのような制約はないことに注意してください。その後、Static Synchronizedは、クラスのすべてのインスタンスへのアクセスを制御するために発生します。静的同期された同期は、スレッドを制限して、JVMのクラスのすべてのインスタンスに同時にアクセスし、対応するコードにすばやくアクセスします。実際、クラスのメソッドまたはコードブロックに同期している場合、このクラスのインスタンスを生成した後、クラスは迅速な監視を行い、同期された修正インスタンスへのスレッドの同時アクセスが迅速に保護されます。静的同期は、監視の1つであり、2つの違いです。つまり、同期はこれと同等であり、同期されたものであり、静的同期は何かに相当します。
日本の著者であるジー・チェンガオの「Java Multithreaded Design Pattern」には、次のようなコラムがあります。
pulbic class someint(){public synchronized void issynca(){} public synchronized void issyncb(){} public static synchronized void csynca(){} public static synchronized void csyncb(){}}}}次に、何かクラスの2つのインスタンスAとBが追加された場合、次のメソッドグループに複数のスレッドで同時にアクセスできるのはなぜですか? axissynca()およびx.issyncb()
bxissynca()およびy.issynca()
cxcsynca()およびy.csyncb()
dxissynca()and something.csynca()
ここで、それが判断できることは明らかです:
A、同じインスタンスの同期ドメインへのアクセスがあるため、同時にアクセスすることはできません。 Bはさまざまなインスタンス用であるため、同時にアクセスできます。静的同期であるため、異なるインスタンスはまだ制限されています。これはsomething.issynca()およびsomeint.issyncb()に相当するため、同時にアクセスすることはできません。
したがって、D?はどうですか、本の答えに同時にアクセスできます。答えの理由は、同期されたものであるため、インスタンスメソッドと同期クラスメソッドはロックとは異なるためです。
個人分析とは、同期された静的同期が2つのギャングと同等であり、それぞれが独自のコントロールを持ち、互いに制約がなく、同時にアクセスできることを意味します。 Java内部設計に同期がどのように実装されているかは明らかではありません。
結論:A:同期静的は、特定のクラスの範囲です。同期された静的CSYNC {}は、複数のスレッドが同時にこのクラスで同期された静的メソッドにアクセスするのを防ぎます。クラスのすべてのオブジェクトインスタンスで動作します。
B:同期は、インスタンスの範囲です。同期Issync(){}は、複数のスレッドが同時に同期されたメソッドにアクセスするのを防ぎます。
2。同期された方法と同期コードを速くすることの違い
同期されたメソッド(){}と同期(this){}には違いはありませんが、同期されたメソッド(){}は読解力に便利ですが、同期(this){}は競合制限領域をより正確に制御でき、より効率的に実行できます。
3。同期されたキーワードを継承することはできません