1。マルチスレッドの紹介
プログラミングでは、ほとんどのビジネスシステムで同時処理が必要であるため、マルチスレッドプログラミングの問題を回避することはできません。同時シナリオにある場合、マルチスレッドは非常に重要です。さらに、インタビュー中に、インタビュアーは通常、次のようなマルチスレッドについて質問します。スレッドの作成方法は?通常、このように答えますが、2つの主な方法があります。 1つ目は次のとおりです。スレッドクラスを継承し、実行方法を書き直します。 2つ目は、実行可能なインターフェイスを実装し、実行方法を書き直します。その後、インタビュアーは間違いなく、これら2つの方法の利点と短所が何であるかを尋ねます。何があっても、私たちは結論に達します。つまり、オブジェクト指向の支持者は継承を少なくし、組み合わせを可能な限り使用しようとするためです。
現時点では、マルチスレッドの返品値を取得したい場合に何をすべきかを考えることもできますか?私たちがもっと学んだ知識に基づいて、呼び出し可能なインターフェイスを実装し、コールメソッドを書き換えることを考えます。実際のプロジェクトでは、多くのスレッドをどのように使用していますか?彼らはいくつの方法を持っていますか?
まず、例を見てみましょう。
これは、マルチスレッドを作成する簡単な方法であり、理解しやすい方法です。この例では、さまざまなビジネスシナリオによると、異なるパラメーターをthread()に渡して、異なるビジネスロジックを実装できます。ただし、マルチスレッドを作成するこの方法によって暴露される問題は、スレッドを繰り返し作成することであり、スレッドを作成した後に破壊する必要があります。同時シナリオの要件が低い場合、この方法は問題ないようですが、スレッドの作成は非常にリソースを消費するため、この方法は不可能です。経験によれば、正しい方法はスレッドプールテクノロジーを使用することです。 JDKは、選択できるさまざまなスレッドプールタイプを提供します。特定の方法については、JDKドキュメントを確認できます。
このコードで注意する必要があるのは、渡されたパラメーターが構成したスレッドの数を表すことです。もっと良いですか?確かにそうではありません。スレッドの数を構成するときは、サーバーのパフォーマンスを完全に考慮する必要があるためです。より多くのスレッド構成がある場合、サーバーのパフォーマンスは優れていない場合があります。通常、マシンによって完了した計算は、スレッドの数によって決定されます。スレッドの数がピークに達すると、計算を実行できません。 CPU(より多くの計算)を消費するのはビジネスロジックである場合、スレッドとコアの数がピークに達します。 I/O(ファイルの操作、ファイルのアップロード、ダウンロードなど)を消費するのはビジネスロジックである場合、スレッドが多いほどスレッドが増えると、特定の意味でパフォーマンスを改善するのに役立ちます。
スレッドの数を設定する別の式:
y = n*((a+b)/a)、n:cpuコアの数、a:スレッドが実行されるときのプログラムの計算時間、b:スレッドが実行されたときのプログラムのブロック時間。この式を使用すると、スレッドプールのスレッドカウント構成が制約され、マシンの実際の状況に従って柔軟に構成できます。
2。マルチスレッドの最適化とパフォーマンスの比較
スレッドテクノロジーは最近のプロジェクトで使用されており、使用中に多くのトラブルに遭遇しました。人気を活用して、いくつかのマルチスレッドフレームワークのパフォーマンス比較を整理します。マスターしたものは、最初のタイプの3つのタイプにほぼ分割されています:ThreadPool(Thread Pool) + CountDownLatch(プログラムカウンター)、2番目のタイプ:Fork/Join Framework、および3番目のタイプのJDK8パラレルストリーム。これらのメソッドのマルチスレッドパフォーマンスの比較要約を次に示します。
まず、複数のファイルオブジェクトがメモリで生成されるビジネスシナリオを仮定します。ここでは、30,000のスレッドスリープが暫定的に決定され、ビジネス処理のビジネスロジックをシミュレートして、これらの方法のマルチスレッドパフォーマンスを比較します。
1)単一のスレッド
この方法は非常に簡単ですが、プログラムは処理中に非常に時間がかかり、各スレッドが実行される前に実行されるのを待っているため、長い間使用されます。マルチスレッドとはほとんど関係がないため、効率は非常に低いです。
最初にファイルオブジェクトを作成します。コードは次のとおりです。
public class fileinfo {private string filename; //ファイル名プライベート文字列filetype; //ファイルタイププライベート文字列filesize; //ファイルサイズプライベート文字列filemd5; // md5コードプライベート文字列fileversionno; //ファイルバージョン番号public fileinfo(){super(); } public fileInfo(string filename、string filetype、string filesize、string filemd5、string fileversionno){super(); this.fileName = filename; this.filetype = filetype; this.filesize = filesize; this.filemd5 = filemd5; this.fileversionno = fileversionno; } public string getFileName(){return filename; } public void setFileName(string filename){this.filename = filename; } public string getFileType(){return filetype; } public void setFileType(String filetype){this.filetype = filetype; } public string getFileSize(){return filesize; } public void setFilesize(string filesize){this.filesize = filesize; } public string getFilemd5(){return filemd5; } public void setFilemd5(String filemd5){this.filemd5 = filemd5; } public String getFileVersionNo(){return fileversionno; } public void setFileVersionno(String fileversionno){this.fileversionno = fileversionno; }次に、ビジネス処理をシミュレートし、30,000個のファイルオブジェクトを作成し、スレッドが1MSの間スリープし、1000msの前にセットを設定し、時間が非常に長く、日食全体が詰まっているため、時間を1msに変更します。
public class test {private static list <fileInfo> fileList = new ArrayList <FileInfo>(); public static void main(string [] args)throws arturnedexception {createfileinfo(); long starttime = system.currenttimemillis(); for(fileInfo fi:filelist){shood.sleep(1); } long endtime = system.currenttimemillis(); System.out.println( "シングルスレッド時間消費:"+(endtime-starttime)+"ms"); } private static void createfileInfo(){for(int i = 0; i <30000; i ++){filelist.add(new fileInfo( "IDカードの正面写真、「JPG」、101522"、「MD5 "+I、" 1 ")); }}}テスト結果は次のとおりです。
30,000個のファイルオブジェクトを生成するには、1分近く長い時間がかかり、効率が比較的低いことがわかります。
2)ThreadPool(Thread Pool) +CountDownLatch(プログラムカウンター)
名前が示すように、CountDownLatchはスレッドカウンターです。その実行プロセスは次のとおりです。最初に、await()メソッドがメインスレッドで呼び出され、メインスレッドがブロックされ、プログラムカウンターがパラメーターとしてスレッドオブジェクトに渡されます。最後に、各スレッドがタスクの実行を終了した後、タスクの完了を示すためにCountDown()メソッドが呼び出されます。 CountDown()が複数回実行された後、メインスレッドの待機()が無効になります。実装プロセスは次のとおりです。
public class test2 {private static executorservice executor = executors.newfixedthreadpool(100); Private Static CountDownLatchカウントダウンラッチ= new CountDownLatch(100); private static list <fileInfo> fileList = new ArrayList <FileInfo>(); private static list <list <fileinfo >> list = new ArrayList <>(); public static void main(string [] args)throws arturnedexception {createfileinfo(); addList(); long starttime = system.currenttimemillis(); int i = 0; for(list <fileInfo> fi:list){executor.submit(new filerunnable(countdownlatch、fi、i)); i ++; } countDownLatch.Await(); long endtime = system.currenttimemillis(); executor.shutdown(); system.out.println(i+"スレッドが時間がかかります:"+(endtime-starttime)+"ms"); } private static void createfileInfo(){for(int i = 0; i <30000; i ++){filelist.add(new fileInfo( "front idカード写真"、 "jpg"、 "101522"、 "md5"+i、 "1")); }} private static void addList(){for(int i = 0; i <100; i ++){list.add(filelist); }}}FileRunnableクラス:
/** * Multithreaded Processing * @author wangsj * * @param <t> */public class filerunnable <t> runnable {private countdownlatch countdownlatch;プライベートリスト<T>リスト;プライベートINT I; public filerunnable(CountDownLatch CountDownLatch、list <t> list、int i){super(); this.CountDownLatch = CountDownLatch; this.list = list; this.i = i; } @Override public void run(){for(t t:list){try {thread.sleep(1); } catch(arturnedexception e){e.printstacktrace(); } CountDownLatch.CountDown(); }}}テスト結果は次のとおりです。
3)フォーク/参加フレームワーク
JDKはバージョン7で始まり、Fork/Joinフレームワークが登場しました。文字通りの観点から、フォークは分割されており、参加は合併であるため、このフレームワークのアイデアはそうです。フォークを通してタスクを分割してから、スプリット文字が実行されて要約された後、結果をマージします。たとえば、継続的に追加されたいくつかの数値、2+4+5+7 =? 、フォーク/結合フレームワークをどのように使用してそれを完了しますか?アイデアは、分子タスクを分割することです。この操作を2つのサブタスクに分割できます。1つは2+4を計算し、もう1つは5+7を計算できます。これがフォークのプロセスです。計算が完了した後、これら2つのサブタスクの計算の結果が要約され、合計が取得されます。これが参加プロセスです。
Fork/Join Framework実行のアイデア:最初に、タスクを分割し、フォーククラスを使用して大きなタスクをいくつかのサブタスクに分割します。このセグメンテーションプロセスは、分割されたタスクが十分に小さくなるまで、実際の状況に従って決定する必要があります。次に、Joinクラスがタスクを実行し、分割されたサブタスクは異なるキューにあります。いくつかのスレッドはキューからタスクを取得し、それらを実行します。実行結果は別のキューに配置されます。最後に、スレッドが開始され、結果がキューで取得され、結果がマージされます。
Fork/Join Frameworkの使用には、いくつかのクラスが使用されています。クラスの使用については、JDK APIを参照できます。このフレームワークを使用すると、Forkjointaskクラスを継承する必要があります。通常、サブクラスの再usiveTaskまたは再usivectionを継承するだけです。 Recursivetaskは、返品結果があるシーンに使用され、再usiveActionは返品結果のないシーンに使用されます。 Forkjointaskの実行には、Forkjoinpoolの実行が必要です。これは、さまざまなタスクキューに追加された分割されたサブタスクを維持するために使用されます。
これが実装コードです:
パブリッククラスのtest3 {private static list <fileinfo> filelist = new arrayList <fileInfo>(); // private static forkjoinpool forkjoinpool(100); // private static job <fileinfo> job = new job <>(filelist.size()/100、filelist); public static void main(string [] args){createfileinfo(); long starttime = system.currenttimemillis(); forkjoinpool forkjoinpool = new forkjoinpool(100); //タスクジョブを分割<fileInfo> job = new job <>(filelist.size()/100、filelist); //タスクを送信して結果を返しますforkjointask <integer> fjtresult = forkjoinpool.submit(job); // block while(!job.isdone()){system.out.println( "タスク完了!"); } long endtime = system.currenttimemillis(); system.out.println( "Fork/Join Framework Time-suming:"+(endtime-starttime)+"ms"); } private static void createfileInfo(){for(int i = 0; i <30000; i ++){filelist.add(new fileInfo( "front idカード写真"、 "jpg"、 "101522"、 "md5"+i、 "1")); }}}/** * execute task class * @author wangsj * */public class job <t> recursivetask <integer> {private static final long serialversionuid = 1l;プライベートインクカウント;プライベートリスト<t> Joblist;パブリックジョブ(int count、list <t> joblist){super(); this.count = count; this.joblist = joblist; } /***実行可能なインターフェイスを実装する実行方法と同様にタスクを実行します* /@Override保護されたinteger compute(){//タスクを分割するif(joblist.size()<= count){executejob(); return joblist.size(); } else {//リストを分解して実行できるまでタスクを作成し続けます<recursivetask <long >> fork = new linkedlist <recursivetask <long >>(); //核タスクを分割します。ここでは、二分法法が使用されますint countjob = joblist.size()/2;リスト<t> leftlist = joblist.sublist(0、countjob);リスト<t> rightlist = joblist.sublist(countjob、joblist.size()); //タスクを割り当てるjob leftjob = new job <>(count、leftlist); job rightjob = new job <>(count、rightlist); //タスクを実行してくださいleftjob.fork(); rightjob.fork(); return integer.parseint(reftjob.join()。toString()) +integer.parseint(rightjob.join()。toString()); }} / ***タスクメソッドを実行* / private void executejob(){for(t job:joblist){try {thread.sleep(1); } catch(arturnedexception e){e.printstacktrace(); }}}テスト結果は次のとおりです。
4)JDK8パラレルストリーミング
平行流は、JDK8の新機能の1つです。アイデアは、順番に実行されたストリームを並行フローに変えることです。パラレルフローは、ストリームを複数のデータブロックに分割し、異なるスレッドを使用して異なるデータブロックのストリームを処理し、最後にフォーク/結合フレームワークと同様に、データストリームの各ブロックの処理結果をマージします。
パラレルストリームは、デフォルトでパブリックスレッドプールForkjoinpoolを使用します。スレッドの数は、使用されるデフォルト値です。マシンのコアの数に応じて、スレッドのサイズを適切に調整できます。スレッドの数を調整することは、次の方法で達成されます。
System.setProperty( "Java.util.concurrent.forkjoinpool.common.parallelism"、 "100");
以下はコードの実装プロセスで、非常に簡単です。
public class test4 {private static list <fileInfo> fileList = new ArrayList <FileInfo>(); public static void main(string [] args){// System.setProperty( "java.util.concurrent.forkjoinpool.common.parallelisis"、 "100"); createfileinfo(); long starttime = system.currenttimemillis(); filelist.parallelstream()。 long endtime = system.currenttimemillis(); System.out.println( "jdk8パラレルストリーミング時間:"+(endtime-starttime)+"ms");} private static void createfileInfo(){for(int i = 0; i <30000; i ++){filelist.add(new fileinfo( "カード "、" jpg "、" 101522 "、" md5 "+i、" 1 ")); }}}以下はテストです。スレッドプールの数は初めて設定されていません。デフォルトが使用されます。テスト結果は次のとおりです。
結果はあまり理想的ではなく、長い時間がかかることがわかりました。次に、スレッドプールの数を設定します。つまり、次のコードを追加します。
System.setProperty( "Java.util.concurrent.forkjoinpool.common.parallelism"、 "100");
その後、テストが実行され、結果は次のとおりでした。
今回は時間がかかり、理想的です。
3。概要
上記の状況を要約すると、単一のスレッドを参照として使用するために、最も長い時間がかかるのはネイティブフォーク/結合フレームワークです。スレッドプールの数はここで構成されていますが、スレッドプールの数を持つJDK8の平行ストリームはより低いです。パラレルストリーミングを実装するコードはシンプルで理解しやすく、ループ用の追加を記述する必要はありません。すべてのパラレルストリームメソッドを完了することができ、コードの量は大幅に削減されます。実際、並列ストリーミングの基礎となる層は依然としてフォーク/結合フレームワークであり、開発プロセス中にさまざまなテクノロジーを柔軟に使用して、さまざまなテクノロジーの利点と欠点を区別して、より良いサービスを提供する必要があります。