マルチスレッドとは何ですか?マルチスレッドを実現する方法は?


転送元:https //blog.csdn.net/csdnnews/article/details/82321777

プロセスとは何ですか?

コンピュータ内で別々に実行されるプログラムはたくさんあり、各プログラムには独立したプロセスがあり、プロセスは互いに独立して存在します。たとえば、下の写真のQQ、Kugouプレーヤー、コンピューターのハウスキーパーなど。ここに画像の説明を挿入

スレッドとは何ですか?

プロセスは、タスクを実行するためにスレッドに依存する必要があります。つまり、プロセスの実行の最小単位はスレッドであり、プロセスには少なくとも1つのスレッドがあります。

では、マルチスレッドとは何ですか?マルチスレッドに関しては、シリアルとパラレルの2つの概念について説明する必要があります。これを理解して初めて、マルチスレッドをよりよく理解できます。

いわゆるシリアルは、実際には複数のタスクを実行するための単一のスレッドに関連しています。ダウンロードファイルを例にとってみましょう。複数のファイルをダウンロードすると、シリアル内で特定の順序でダウンロードされます。はい、つまり、 AのダウンロードがBのダウンロードを開始するのを待つ必要があり、それらが時間的に重複することは不可能です。
ここに画像の説明を挿入
並列:複数のファイルをダウンロードし、複数のスレッドを開き、同時に複数のファイルをダウンロードします。これは厳密な意味で、同時に発生します。並列は時間的に重複します。
ここに画像の説明を挿入
これらの2つの概念を理解した後、マルチスレッドとは何かについて話しましょう。たとえば、Tencent Managerを開きます。TencentManager自体はプログラムです。つまり、プロセスであり、多くの機能があります。下の画像を見て、ウイルスをチェックして殺し、ゴミを片付け、コンピュータを使用できます。加速および他の多くの機能。

シングルスレッドによると、ゴミを片付けるかウイルスをチェックするかにかかわらず、次のことを行う前に、いずれかのことを行う必要があります。実行の順序があります。

マルチスレッドの場合、ウイルス検出やコンピュータアクセラレーションなど、ゴミを片付けるときに実際に実行できますが、厳密には同時に発生し、実行順序はありません。
ここに画像の説明を挿入
上記は、プロセスの実行時に複数のスレッドが生成されることです。

この問題を理解した後、マルチスレッドスレッドセーフを使用するときに考慮しなければならない別の問題を理解する必要があります。

今日は、スレッドの安全性を確保する方法については説明しません。スレッドの安全性とは何ですか?以前のインタビューで聞いたので、正直なところ、これまではこの問題をよく理解していませんでした。スレッドセーフを確保する方法を学んだだけのようですが、いわゆる安全性とは何かわかりません。

スレッドセーフとは何ですか?

複数のスレッドがメソッドにアクセスする場合、メソッドの呼び出し方法やこれらのスレッドの交互の実行方法に関係なく、メインプログラムで同期を行う必要はありません。このクラスの結果の動作は、正しい動作を想定したものです。このクラスはスレッドセーフだと言ってください。
これはスレッドセーフの問題であるため、複数のスレッドにアクセスした場合にすべての隠れた危険が発生することは間違いありません。つまり、複数のスレッドにアクセスしたときに期待される動作に従ってプログラムが動作できることを確認する必要があります。実行するには、次のコードを見てみましょう。

Integer count = 0;
public void getCount() {
       count ++;
       System.out.println(count);
 }

非常に単純なコードで、このメソッドのアクセス数を数えましょう。複数のスレッドが同時にアクセスしても問題はありません。3つのスレッドを開き、各スレッドを10回ループすると、次の結果が得られました。
ここに画像の説明を挿入

ここには2つの26があることがわかります。この状況は、このメソッドがスレッドセーフではないことを明らかに示しています。この問題には多くの理由があります。

最も一般的なのは、スレッドAがメソッドに入った後、countの値を取得し、この値を読み取るだけです。countの値が変更されていない場合、結果としてスレッドBも入ります。スレッドAにつながり、スレッドBが受け取るカウント値は同じです。

したがって、これらすべてがこの共有変数を操作する必要があるため、これは実際にはスレッドセーフクラスではないことがわかります。実際、スレッドセーフを明確に定義することは非常に複雑です。私たちのプログラムに基づいてスレッドセーフが何であるかを要約しましょう。

複数のスレッドがメソッドにアクセスする場合、メソッドの呼び出し方法やこれらのスレッドの交互の実行方法に関係なく、メインプログラムで同期を行う必要はありません。このクラスの結果の動作は、正しい動作を想定したものです。このクラスはスレッドセーフだと言ってください。

スレッドセーフとは何かを理解したので、Javaでスレッドセーフを確保するための2つの最も一般的な方法を見てみましょう。最初にコードを見てみましょう。

public void threadMethod(int j) {
int i = 1;

j = j + i;

}

このコードはスレッドセーフだと思いますか?

完全にスレッドセーフであることは間違いありません。なぜスレッドセーフなのかを分析してみましょう。

このコードには状態がないことがわかります。つまり、コードにスコープが含まれておらず、参照用に他のクラスのドメインを参照していません。実行のスコープと実行結果のみが存在します。はこのスレッドのローカル変数にあり、実行中のスレッドのみがアクセスできます。現在のスレッドのアクセスは、同じメソッドにアクセスする別のスレッドに影響を与えません。

2つのスレッドは同時にこのメソッドにアクセスします。共有データがないため、それらの動作は他のスレッドの操作や結果に影響を与えないため、ステートレスオブジェクトもスレッドセーフです。

ステータスを追加するのはどうですか?

このコードに状態を追加し、このメソッドのヒット数を記録するためにカウントを追加し、リクエストごとにカウント+ 1を追加する場合、このスレッドは現時点で安全ですか?

public class ThreadDemo {

int count = 0; //メソッドのヒット数を記録します

public void threadMethod(int j){

   count++ ;

   int i = 1;

   j = j + i;

}
}

明らかに、もはやそうではありません。シングルスレッド操作では実際には問題はありませんが、複数のスレッドがこのメソッドに同時にアクセスすると、問題が発生します。まず、count +1操作を分析してみましょう。

このメソッドを入力した後、最初にcountの値を読み取り、次にcountの値を変更し、最後にこの値をcountに割り当てる必要があります。「読み取り」1>「変更」1>「割り当て」の合計3つのステップが含まれます。 、このプロセスは段階的であるため、最初に次の図を見て、問題が発生するかどうかを確認し
ここに画像の説明を挿入
ましょう。countの値が正しい結果ではないことがわかります。スレッドAがcountの値を読み取ると、しかし、変更する前に、スレッドBはすでに入っており、スレッドBはcountの値を1として読み取ります。このため、countの値はすでにずれているため、このようなプログラムはコードに配置されます。多くの隠れた危険があります。

スレッドセーフを確保する方法は?

スレッドセーフの問題があるので、この問題を解決する方法を見つける必要があります。どのように解決するのでしょうか。いくつかの一般的な方法について話しましょう

同期

同期キーワードは、スレッドの同期を制御し、マルチスレッド環境でスレッドが複数のスレッドによって同時に実行されないようにし、データの整合性を確保するために使用されます。使用方法は通常、に追加されます。方法。

public class ThreadDemo {

int count = 0; //メソッドのヒット数を記録します

パブリック同期voidthreadMethod(int j){

   count++ ;

   int i = 1;

   j = j + i;

}
}

このようにして、スレッドが確実に同期されるようにすることができます。同時に、誰もが通常無視する問題に注意を払う必要があります。まず、同期によって、コードではなく角かっこでオブジェクトがロックされます。静的同期メソッド、ロックはオブジェクト自体です。これです。

同期ロックがオブジェクトをロックするときに、他のスレッドがロックオブジェクトを取得する場合は、スレッドが実行を終了してロックオブジェクトを解放するまで待機する必要があります。それ以外の場合は、待機状態になっています。

注:synchronizedキーワードを追加するとスレッドの安全性が高まりますが、使用する場合は、synchronizedの使用範囲を狭めることにも注意が必要です。自由に使用すると、プログラムのパフォーマンスなどに影響します。オブジェクトはそれを取得したいのです。ロックすると、ロックを使用せずにロックを占有し続けることになります。これは、リソースの少しの浪費です。

ロック

まず、同期との違いについて説明します。ロックはJava 1.6で導入されました。ロックの導入により、ロックの操作性が向上します。これはどういう意味ですか?つまり、手動でロックを取得して解放する必要がある場合、タイムアウト取得の取得と同期機能を中断することもできますが、使用の観点からは、ロックは明らかに同期されていないため、便利ですばやく使用できます。まず、一般的にどのように使用されているかを見てみましょう。

private Lock lock = new ReentrantLock(); // ReentrantLock是Lock的子类

private void method(Thread thread){ lock.lock(); //ロックオブジェクトを取得しますtry { System.out.println( "Thread name:" + thread.getName()+ "Obtained the lock"); //スレッド.sleep (2000); } catch(Exception e){ e.printStackTrace(); }最終的に{ System.out.println( "thread name:" + thread.getName()+ "released the lock"); lock.unlock (); //ロックオブジェクトを解放する} }










メソッドに入るには、まずロックを取得してからビジネスコードを実行する必要があります。ここでの違いは、ロックによって取得されたオブジェクトを自分で解放する必要があることです。コードの異常を防ぐために、ロック解除操作を実行します。とにかくfinallyのコードが実行されるため、Finallyに配置されます。

mainメソッドを記述し、2つのスレッドを開いて、プログラムが正常かどうかをテストします。

public static void main(String[] args) {
       LockTest lockTest = new LockTest();
   // 线程1
   Thread t1 = new Thread(new Runnable() {

       @Override
       public void run() {
           // Thread.currentThread()  返回当前线程的引用
           lockTest.method(Thread.currentThread());
       }
   }, "t1");

   // 线程2
   Thread t2 = new Thread(new Runnable() {

       @Override
       public void run() {
           lockTest.method(Thread.currentThread());
       }
   }, "t2");

   t1.start();
   t2.start();

}

その結果、
ここに画像の説明を挿入
実装に問題がないことがわかります。

実際、Lockでロックを取得するには、いくつかの方法があります。もう1つ、tryLock()はLock()とは異なります。Lockがロックを取得するとき、ロックを取得できない場合は、常に待機しています。 。ロックが取得されるまで状態を示しますが、tryLock()はこのようにはなりません。TryLockにはブール値の戻り値があります。ロックが取得されない場合、falseを直接返し、待機を停止します。Lock()のように永久に待機することはありません。ロックを取得します。

コードを見てみましょう:

private void method(Thread thread){
       // lock.lock(); // 获取锁对象
       if (lock.tryLock()) {
           try {
               System.out.println("线程名:"+thread.getName() + "获得了锁");
               // Thread.sleep(2000);
           }catch(Exception e){
               e.printStackTrace();
           } finally {
               System.out.println("线程名:"+thread.getName() + "释放了锁");
               lock.unlock(); // 释放锁对象
           }
       }
   }

結果:現在も2つのスレッドを使用してテストを行っていますが、スレッドt1がロックを取得した後、スレッドt2がすぐに入り、ロックが既に占有されていることがわかり、現時点では待機していません。

ここに画像の説明を挿入
この方法は完璧ではないようです。最初のスレッドが2番目のスレッドが入るよりもロックを取得するのに時間がかかる場合、ロックオブジェクトを取得することも不可能ですか?

次に、待機中のスレッドが5秒間待機できるように制御できますか?5秒後にロックを取得できない場合は、停止して待機します。実際には、tryLock()を待機に設定できます。対応する時間。

private void method(Thread thread) throws InterruptedException {
       // lock.lock(); // 获取锁对象
   // 如果2秒内获取不到锁对象,那就不再等待
   if (lock.tryLock(2,TimeUnit.SECONDS)) {
       try {
           System.out.println("线程名:"+thread.getName() + "获得了锁");

           // 这里睡眠3秒
           Thread.sleep(3000);
       }catch(Exception e){
           e.printStackTrace();
       } finally {
           System.out.println("线程名:"+thread.getName() + "释放了锁");
           lock.unlock(); // 释放锁对象
       }
   }

}

結果:上記のコードを見ると、ロックオブジェクトを取得するときに2秒待つことができますが、スレッドt1はロックオブジェクトを取得してからタスクを実行するのに3秒かかり、スレッドt2はこれを待機していません。の時間。
ここに画像の説明を挿入
この待機時間を5秒に変更して、結果を見てみましょう。

private void method(Thread thread) throws InterruptedException {
       // lock.lock(); // 获取锁对象
   // 如果5秒内获取不到锁对象,那就不再等待
   if (lock.tryLock(5,TimeUnit.SECONDS)) {
       try {
           System.out.println("线程名:"+thread.getName() + "获得了锁");
       }catch(Exception e){
           e.printStackTrace();
       } finally {
           System.out.println("线程名:"+thread.getName() + "释放了锁");
           lock.unlock(); // 释放锁对象
       }
   }

}

結果:この時点で、スレッドt2がロックオブジェクトを取得するために5秒間待機し、タスクコードを実行していることがわかります。
ここに画像の説明を挿入
上記は、スレッドセーフを確保するためにロックを使用する方法です。

おすすめ

転載: blog.csdn.net/weixin_45682070/article/details/107413334