1並行性の問題は何ですか。
同じリソースに同時に(または同じ期間)にアクセスする複数のプロセスまたはスレッドは、同時実行の問題を引き起こします。
典型的な例は、2つの銀行事業者が同時に同じアカウントを運営することです。たとえば、オペレーターAとBは、同時に1,000元の残高のアカウントを読み、オペレーターAはアカウントに100元を追加し、オペレーターBはアカウントの50元も減らし、Aは最初に提出し、Bは後で提出します。最後の実際のアカウント残高は1000-50 = 950元ですが、1000+100-50 = 1050である必要があります。これは典型的な並行性の問題です。それを解決する方法は?ロックを使用できます。
Javaで同期した2USAGE
使用法1
public class test {public synchronized void print(){….;}}スレッドがprint()メソッドを実行すると、オブジェクトがロックされます。他のスレッドは、オブジェクトのすべての同期ブロックを実行できません。
使用法2
public class test {public void print(){synchronized(this){//このオブジェクトをロックする…;}}}}同じ使用法1ですが、同期された使用の本質をよりよく反映することができます。
使用3
public class test {private string a = "test"; public void print(){synchronized(a){// lock a object ...;}} public synchronized void t(){…; //print()の実行はオブジェクトをロックします。テストのオブジェクトをロックしていないことに注意してください。つまり、テストオブジェクトの他の同期メソッドは、print()のためにロックされません。同期コードブロックが実行された後、Aへのロックがリリースされます。
オブジェクトの他の同期ブロックの高性能ライティングに影響を与えることなく、オブジェクトのコードブロックをロックするために:
public class test {private byte [] lock = new byte [0]; public void print(){synchronized(lock){…;}} public synchronized void t(){…;}}静的メソッドロック
public class test {public synchronized static void execute(){…;}}同じ効果
public class test {public static void execute(){synchronized(testthread.class){…;}}}}}ジャワに3つのロックして、トイレに行くためにキューに行きます。
ロックは、他のプロセスやスレッドがリソースにアクセスするのを防ぐ方法です。つまり、ロックされたリソースに他のリクエストでアクセスすることはできません。 Javaでは、相乗的なキーワードを使用してオブジェクトをロックします。例えば:
public class mystack {int idx = 0; char [] data = new char [6]; public synchronized void push(char c){data [idx] = c; idx ++;} public synchronized char pop(){idx - ; return data [idx];} public static void main(String args [])厳密に言えば、オブジェクトMのすべての同期ブロックがロックされています。 Mにアクセスしようとする別のスレッドTがある場合、TはMオブジェクトのプッシュメソッドとポップメソッドを実行できません。 */m.pop(); //オブジェクトMがロックされています。 }}Javaのロックアップ解除は、公共のトイレに並んでいる複数の人々とまったく同じです。最初の人は入った後、内側からドアをロックしたので、他の人は並んで待たなければなりませんでした。ドアは、終了後に最初の人が出てきたときにのみ開きます(ロック解除)。それは2人目の人が入る番であり、彼は再び内側からドアをロックし、他の人は並んで待ち続けました。
トイレの理論を使用して理解するのは簡単です。人がトイレに入ると、トイレが施錠されますが、1人の人が同時に2つのトイレでしゃがむことができないため、他のトイレをロックすることはありません。 Javaの場合、それは意味します:Javaのロックは、クラスではなく同じオブジェクトにターゲットにされています。次の例を参照してください。
mystatckm1 = newmyStack(); mystatckm2 = newmyStatck(); m1.pop(); m2.pop();
M1オブジェクトのロックは、同じトイレの位置ではないため、M2のロックに影響しません。つまり、3つのスレッドT1、T2、およびT3の動作M1がある場合、これらの3つのスレッドはM1でのみキューと待機できます。他の2つのスレッドT8、T9がM2を動作させると仮定すると、T8、T9はM2でのみ待機します。 T2とT8はそれとは何の関係もありません。 M2のロックがリリースされたとしても、T1、T2、およびT3はM1でキューする必要がある場合があります。理由はありません。それは同じ便座ではありません。
Javaは、2つのロックを同時にコードブロックに追加することはできません。これは、データベースロックメカニズムとは異なります。データベースは、いくつかの異なるロックを同時にレコードに追加できます。
4ロックをリリースする必要がありますか?
一般に、ロックは、同期コードブロック(ロックコードブロック)が実行された後にリリースされるか、ロックをWAIT()メソッドの途中でリリースできます。 wait()方法は、トイレで途中でしゃがむようなもので、突然下水道がブロックされていることがわかりました。下水道修理業者(通知を実行する準備ができているスレッド)がトイレに入ってクリアできるように、私は出てきて脇に立ちなければなりませんでした。ブロックを解除した後、マスターは「修理された」と叫んだ。今出てきた同志は、それを聞いた後、再び並んでいた。注意してください、あなたはマスターが出てくるのを待たなければなりません。マスターが出てこない場合、誰も入ることができません。つまり、通知の後、他のスレッドはブロックされた領域に入り、すぐに動き回ることができますが、他のスレッドは、通知コードが実行されてリリースされてロックを解放するブロックされた領域の後に入ることができます。
これが待機と通知コードの例です。
public synchronized char pop(){char c; while(buffer.size()== 0){try {this.wait(); //トイレから外出} catch(//無視する…//それを無視する…}} charvalue(); return c;} public synchronized void push(char c){this.notify(); //それらのwait()スレッドに通知して、再びキューアップします。注:再洗浄するように通知するだけです。文字charobj = new Character(c); buffer.Addelement(charobj);} //ロックを実行してリリースします。これらのキューに囲まれたスレッドが入ることができます。もっと深くなります。
wait()操作により、途中で出てきた同志は、通知信号を受け取るまで列に並んでいませんでした。彼は、人々が彼の隣に並んでいるのを見ました(水道管修理マスターもその中にいます)。水道管修理マスターはラインに行くことができず、トイレに行く人のように列に並ぶ必要があることに注意してください。人が途中でしゃがんだ後、水道管修理マスターが突然現れてすぐに修理することができるということではありません。彼は、普通の糸でもあるので、元々列に並んでいた人々と公正に競争したいと思っています。水道管修理マスターが後ろに並んでいる場合、前の人はそれがブロックされて待っていることに気づき、その後、出て、脇に立ち、待って、出て、脇に立ち、マスターに行って通知を実行します。このようにして、しばらくすると、列の隣にたくさんの人が立って、通知を待っています。
最後に、マスターが入って通知しました。次は何ですか?
1.待機者(スレッド)に通知されます。
2。なぜ彼は別の待機者の代わりに通知されているのですか? JVMに依存します。私たちは先制することはできません
どちらが通知されるかを決定します。言い換えれば、優先度が高い人は最初に目覚めないかもしれませんが、待っています
いつでも最初に目覚めたわけではありません。すべてが予測不可能です! (もちろん、JVMを知っている場合
実装されている場合は、予測できます)。
3。彼(通知されたスレッド)は、再びキューを整える必要があります。
4。彼は最初の場所にいますか?答えは次のとおりです。わからない。彼は最後のものになりますか?必ずしもそうではありません。
しかし、スレッドの優先度が比較的高く設定されている場合、最初にランク付けされる可能性は比較的高くなります。
5.便座に再び入るのが彼の番であるとき、彼は最後の待機()の場所から実行を続け、再実行しません。
嫌な方法でそれを置くために、彼は再びとりとめのないように、とりとめのないものになり続けます。
6.マスターNotifyAll()の場合、途中であきらめたすべての人々が再び並んでいます。順序は不明です。
Javadocは、TheawakedThreakedswillnotbeabletopro comefurthecurrentthreadrelinquishthelockonthisobject(現在のスレッドがロックを解放する前に、目覚めたスレッドは実行できない)と言います。
これは、便座理論を使用して説明することは明らかです。
5ロックの使用
同期されたキーワードを使用して、リソースをロックします。ロックキーワードも問題ありません。 JDK1.5では新しいです。使用法は次のとおりです。
class BoundedBuffer {final lock = new ReentrantLock(); final resinterlock(); final条件notfull = lock.newcondition(); final条件Notempty = lock.newcondition(); final object = new Object [100]; int putptr、takeptr、count; count; public void put(object x)throws interruptedexception {lock.lock(); lock.lock(); notfull.await(); items [putptr] = x; if(++ putptr == items.length)putptr = 0; ++ count; notempty.signal();}最後に{lock.unlock();}} public object take()throws interruptedexception {lock.lock(); try {obbles x = 0); items [takeptr]; if(++ takeptr == items.length)takeptr = 0; - count; notfull.signal(); return x;} fullly {lock.unlock();}}}}}(注:これはJavadocの例です。これは、ブロッキングキューの実装例です。いわゆるブロッキングキューは、キューがいっぱいまたは空の場合、スレッドのブロックと待機を引き起こすことを意味します。
オブジェクトのlock.lock()とlock.unlock()の間のコードはロックされます。同期するのと比較して、この方法の良いところは何ですか?要するに、それは待機スレッドを分類します。便座の理論を使用して説明するために、途中でしゃがみ、便座から出て待つ人はさまざまな理由があるかもしれません。トイレがブロックされているため、トイレが水がなくなったためにはあるものもあります。通知すると、叫ぶことができます。トイレがブロックされるのを待つと、並んでいる場合(たとえば、トイレの問題が解決されました)、またはトイレがブロックされるのを待つと叫びます(たとえば、トイレの問題が解決されました)。これにより、より詳細な制御が可能になります。待機と同期して通知するのとは異なり、トイレがブロックされているかトイレが水っぽくないかどうかは、叫ぶことができます。並んでいる人々が入って見ると、彼らはトイレの問題が解決されているだけであり、彼らが解決したいという問題(トイレには水がない)はまだ解決されていないので、彼らは戻って待って(待って)、時間とリソースを無駄にして散歩に来なければなりません。
ロック方法と同期の間の対応する関係:
lockawaitsignalsignalall
SynchronizedWaitNotifyNotifyAll
注:ロックによってロックされたブロックで待機、通知、NOTIFYALLを呼び出しないでください
6.パイプラインを使用して、スレッド間で通信します
原則は簡単です。 2つのスレッド、1つはpipedinputStreamを操作し、もう1つはPipedOutputStreamを操作します。 PipedOutputStreamによって記述されたデータは、最初にバッファーでキャッシュされます。バッファがいっぱいの場合、このスレッドが待機します。 PipedInputStreamは、バッファ内のデータを読み取ります。バッファにデータがない場合、このスレッドは待機します。
JDK1.5でキューをブロックすることにより、同じ関数を実現できます。
パッケージio; Import java.io。*; public class pipedstreamtest {public static void main(string [] args){pipedoutputstream ops = new pipedoutputStream(); pipedinputStream pis = new pipedinputStream(); try {ops.connect(pis);//pipeLine(pisnect); Consumer(pis).run();} catch(Exception e){e.printstacktrace();}} //プロデューサークラスプロデューサーはRunnable {private pipedOutputStream ops; public Producer(pipedoutputStream ops){this.ops = ops;}公開void run(){try {ops.write( "hell、spell" .getbytes()); ops.close();} catch(expering e){e.printstacktrace();}}}}} // runnable {private pipedinputStream pis; pipedinputStream pis = pist;} pist;} run(){try {byte [] bu = new byte [100]; int len = pis.read(bu); system.out.println(new String(bu、0、len)); pis.close();} catch(exceance e){e.printstacktrace();}}}}}例2:上記のプログラムへの少し変更は2つのスレッドになります。
パッケージio; import java.io。*; public class pipedstreamtest {public static void main(string [] args){pipedoutputStream ops = new PipedOutputStream(); PipedInputStream Pis = new PipedInputStream(); new PipedInputStream(); try {ops.connect(pis);/new PipeLine p = new PipeLine p = new PipeLine Pipelin thread(p).start(); Consumer c = new Consumer(pis); newスレッド(c).Start();} catch(E.PrintStackTrace();}}}}}}} //プロデューサークラスのプロデューサーRunnable {private pipedOutputStream ops; run(){try {for(;;){ops.write( "hell、spell" .getbytes()); ops.close();}}}}} // runnable {private pipedinputstream pis; public Consumer(pipedinputstream pis){pis = pis = pis;} pis;} pis;} pis;} (;;){byte [] bu = new byte [100]; int len = pis.read(bu); system.out.println(new String(bu、0、len));} pis.close();} catch(exceance e){e.printstacktrace();}}}}}}例3。この例はアプリケーションにより適しています
Java.io。*;パブリッククラスPipedio {//プログラムの実行後、送信ファイルファイルのコンテンツをReceiverFileファイルにコピーしてpublic static void main(try {//読み取りおよび書き込みオブジェクトPipedinputStream pis = assoationputStream = new pipedutputputStream(); PipeduttutputputStream( pos.connect(pis); // 2つのスレッドを作成して開始します。新しい送信者(pos、 "c:/text2.txt") Sender(pipedoutputStream pos、string filename){this.pos = pos; file = new file(filename);} // public void run(){// read {// read file content fileinputstream fs = new fileinputstream(file); int data; pos.write(data);} pos.close();} catch(ioexception e){system.out.println( "sender error" +e);}} //スレッドクラスレシーバー{pipedinputstream pis; file file; file file; //構築方法レシーバー(pipedinputstream receiver、pis; pis = pis) file(filename);} //スレッドruns public void run(){// write file file object fileoutputStream fs = new fileoutputStream(file); int data; // read while((data = pis.read())!= -1){//ローカルファイルfs.writeへの書き込み(データ) e){system.out.println( "受信エラー" +e);}}}7ブロッキングキュー
ブロッキングキューは、パイプラインフロー方法を置き換えて、インレット/排水パイプモード(生産者/消費者)を実装できます。 JDK1.5は、いくつかの既製のブロッキングキューを提供します。次に、次のようにarrayblockingqueueのコードを見てみましょう。
これがブロッキングキューです
blockingqueue blockingq = new arrayblockingqueue 10;
キューからスレッドが取られます
for(;;){object o = blockingq.take(); // queueは空です。別のスレッドがキューに保存されます
for(;;){blockingq.put(new object()); //キューがいっぱいの場合、待機(ブロック)}}ブロッキングキューは、パイプラインよりも使用する方が簡単であることがわかります。
8Use Executor、Executor、Executorservice、ThreadPoolexecutor
スレッド管理タスクを使用できます。また、JDK1.5が提供する一連のクラスを使用して、タスクをより便利に管理することもできます。これらのカテゴリから、タスク指向の考え方を体験できます。これらのクラスは次のとおりです。
執行者インターフェイス。使い方:
executor executor = Anexecutor; // executorインスタンスを生成します。 executor.execute(new runnabletask1());
意図:ユーザーはタスクの実行にのみ焦点を合わせており、タスクの作成と実行の詳細、およびサードパーティの実装者が懸念している他の問題について心配する必要はありません。つまり、タスクコールの実行とタスクの実装を分離します。
実際、JDK1.5にはこのインターフェイスの優れた実装がすでにあります。十分。
執行者は、さまざまなインターフェイスのインスタンスを生成するために使用されるコレクションのような工場クラスまたはツールクラスです。
executorserviceインターフェイスはexecutor.executorから継承されます。実行者は実行のためにタスクをexecuter()にスローし、残りを無視します。 ExecutorServiceは異なり、より多くの制御作業を行います。例えば:
class Networkservice {プライベートファイナルサーバーソケットサーバーソケット;プライベートファイナルエグゼクティブサービスプール; public Networkservice(int port、int poolsize)throws ioexception {serversocket = new Serversocket(PORT); pool = executors.newfixedthreadpool(poolsize);} public void(){try {;; Handler(Serversocket.Accept());}} catch(IoException ex){pool.shutdown(); //新しいタスクは実行されません}}}クラスハンドラーはrunnable {private final socket socket; handler(socket socket){this.socket = socket = socket;} public void run(){//executorservice(つまり、コード内のプールオブジェクト)がシャットダウンを実行した後、新しいタスクを実行することはできなくなりましたが、古いタスクは引き続き実行され、実行を待っている人はもはや待たなくなります。
タスクの提出者とパフォーマーコミュニケーション
public static void main(string args [])throws execust {executorservice executor = executors.newsinglethreadexecutor(); callable task = new callable(){public string call(){public string call()throws {return "test";}}; future f = executor.submit(task); string result rurce system.out.println(result); executor.shutdown();}executor.newsinglethreadexecutor()が取得したexecutorインスタンスには、次の特性があります。
タスク実行は順番に。例えば:
executor.submit(task1); executor.submit(task2);
タスク2を実行する前に、タスク1が実行されるのを待つ必要があります。
task1とtask2はキューに配置され、ワーカースレッドによって処理されます。つまり、合計に2つのスレッドがあります(メインスレッド、タスクを処理するワーカースレッド)。
他のクラスについては、Javadocを参照してください
9同時プロセス制御
このセクションの例は、Wen ShaoのJava Concurrencyチュートリアルからのものであり、変更される場合があります。ウェン氏に敬礼。
CountDownLatchドアピンカウンター
スレッドを起動し、スレッドが終了するのを待ちます。つまり、一般的なメインスレッドと他の子スレッドは、メインスレッドの終了後に実行されます。
public static void main(string [] args)スロー例外{// dodo auto-finalated method stub final int count = 10; final countdownlatch completelatch = new countdownlatch(count); //ドアラッチ数は10 for(int i = 0; i <count; i ++){new swrew = new void run( completelatch.countdown(); // 1つのドアラッチ}}; thread.start();} completelatch.await(); //ドアラッチがまだ縮小されていない場合は、待ってください。 }JDK1.4では、一般的な方法は、子スレッドのステータスとメインスレッドのループ検出を設定することです。使いやすさと効率は良くありません。
多くのスレッドを開始し、通知が開始されるのを待ちます
public static void main(string [] args)スロー例外{// dodo auto-finalated method stub final countdownlatch startlatch = new countdownlatch(1); //(int i = 0; i <10; i ++){スレッドスレッド= newスレッド( "workerswred"+i){public void run(){try buse();まだ削減された、wait} catch(arternedexception e){} // do xxxx}}; shood.start();} startlatch.countdown(); // 1つのドアラッチを減らす}Cyclibarrier。すべてのスレッドがスタートラインに到達した後にのみ、それらは実行を続けることができます。
パブリッククラスのCyclibarriertestは、実行可能{プライベートサイクリバリエバリア;パブリックサイクリバリエラート(cyclibarrierバリア){this.barrier = barrier;} public void run(){// do xxxx; try {this.barrier.await();到着しない場合は、待ち続けてください。すべてに到達したら、バリアの実行機能ボディの内容を実行します} catch(例外e){}}/** * @param args */public static void main(string [] args){//パラメーター2は、両方のスレッドがスタートラインに到達したことを意味します。 {// do xxxx;}});スレッドt1 = newスレッド(new cyclibarriertest(バリア));スレッドt2 = newスレッド(new cyclibarriertest(バリア)); t1.start(); t2.start();}}}}これにより、カウンター + wait/notifyallでこの関数を実装する従来の方法が簡素化されます。
10並行性3の法律
アムダールの法律。問題のスケールを考えると、並列化パーツは12%を占めています。次に、並列性が極端に適用されたとしても、システムのパフォーマンスは1/(1-0.12)= 1.136倍のみを改善できます。つまり、並列処理には、システムのパフォーマンスの向上に上限があります。
グスタフソンの法律。 Gustafsonの法律は、Amdahlの法律は、CPUの数が増えるにつれて、より多くのコンピューティング能力を使用できるとは考えていないと述べています。本質は、問題の規模を変更して、Amdahlの法則のシリアル処理の残りの88%が並行して、パフォーマンスのしきい値を突破できるようにすることです。本質的に、それは一種のスペース交換時間です。
Sun-ni Law。これは、最初の2つの法律のさらなる昇進です。主なアイデアは、コンピューティングの速度がCPUではなくストレージの速度によって制限されるということです。したがって、ストレージスペースなどのコンピューティングリソースを最大限に活用し、問題の規模を増やして、より良い/より正確なソリューションを生成する必要があります。
11同時から並列へ
コンピューターは、オブジェクトを識別するときに迅速に計算する必要があり、チップが熱くて熱くなるようにする必要があります。人々がオブジェクトを認識するとき、彼らは一目ではっきりしていますが、特定の脳細胞を燃やし(誇張)、不快に感じることはありません。これは、脳が分散並列動作システムであるためです。 Googleがいくつかの安価なLinuxサーバーを使用して巨大で複雑な計算を実行できるように、脳内の無数のニューロンは独立して計算し、結果を互いに共有し、単一のCPUに数兆操作を必要とする効果を即座に完了します。並列処理の分野で作成されている場合、コンピューターの開発と将来に計り知れない影響を与えることを想像してください。もちろん、課題も想像できます。多くの問題は簡単に「分割」されません。
要約します
上記は、Javaの並行性の問題に関するこの記事の全体的な内容であり、すべての人に役立つことを願っています。興味のある友人は、このサイトの他の関連トピックを引き続き参照できます。欠点がある場合は、それを指摘するためにメッセージを残してください。このサイトへのご支援をありがとうございました!