参照:「Javaマルチスレッドプログラミングコアテクノロジー」
1つは、スレッドセーフと非スレッドセーフです。
- スレッドセーフではない:複数のスレッドが同じオブジェクト内のインスタンス変数に同時にアクセスすると、結果はダーティリードになります(ここではデータベースの例を使用してダーティリードを説明します。ここでは、スレッドのrunメソッドと同等です。ローリングはスレッドのrunメソッドが実行されたとき)
ダーティ読み取り:別のトランザクションのロールバックが読み取られる前のダーティデータ。たとえば、トランザクションBの実行中に、データXが変更されます。コミットする前に、トランザクションAはXを読み取りますが、トランザクションBはロールバックします。このようにして、トランザクションAはダーティ読み取りを形成します。
繰り返し不可の読み取り:トランザクションAが最初にデータを読み取り、ロジックが実行されると、トランザクションBがこのデータを変更し、トランザクションAが再度読み取ると、データが一致しないことがわかります。いわゆる繰り返し不可能な読み取り。
ファントム読み取り:トランザクションAは最初に条件インデックスに従ってN個のデータを取得し、次にトランザクションBはこれらのN個のデータ以外のM個のデータを変更するか、トランザクションAの検索条件を満たすM個のデータを追加します。もう一度検索して、N +があることを確認します。M個のデータで、ファントム読み取りが発生します。
- スレッドセーフ:取得したインスタンス変数の値は同期的に処理され、メソッド内の変数はスレッドセーフです
2つの同期同期方式
例を見てみましょう:
public class HasSelfPrivateNum {
private int num = 0;
public void addI(String username){
try {
if(username.equals("a")){
num = 100;
System.out.println("a set over");
Thread.sleep(2000);
}else{
num = 200;
System.out.println("b set over");
}
System.out.println("username= " + username + " num=" + num);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
public class ThreadA extends Thread{
private HasSelfPrivateNum numRef;
public ThreadA(HasSelfPrivateNum numRef) {
this.numRef = numRef;
}
@Override
public void run() {
numRef.addI("a");
}
}
public class ThreadB extends Thread{
private HasSelfPrivateNum numRef;
public ThreadB(HasSelfPrivateNum numRef) {
this.numRef = numRef;
}
@Override
public void run() {
numRef.addI("b");
}
}
public class Run {
public static void main(String[] args) {
HasSelfPrivateNum numRef = new HasSelfPrivateNum();
new ThreadA(numRef).start();
new ThreadB(numRef).start();
}
}
HasSelfPrivateNumは、ビジネスオペレーションメソッドです。着信ユーザー名が「a」の場合、num = 100で、「a setover」を出力します。着信ユーザー名が「b」の場合、num = 200で、「b」を出力します。 ThreadBは2つのスレッドで、Runはメインスレッドです
[外部リンクの画像転送に失敗しました。ソースサイトにホットリンク防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします(img-6R71FfPv-1615786729413)(C:\ Users \ VSUS \ Desktop \ Notes \ Multithreading \ img \ 15.png)]
分析:スレッドAとスレッドBが開始すると、スレッドAは最初にCPUをプリエンプトし、HasSelfPrivateNumのaddIを実行し、num = 100を設定してから、Thread.sleep(2000);を実行します。スレッドAは休止状態であり、スレッドBはCPUを取得します。 、メソッドを入力してnum = 200を設定し、username = b num = 200を出力します。スレッドAがウェイクアップすると、numの値がわからず、num = 200に変更されるため、「username = anum」が出力されます。 = 200 "
addIメソッドに同期ロックを追加するだけで済みます
public class HasSelfPrivateNum {
private int num = 0;
synchronized public void addI(String username){
......
}
}
[外部リンクの画像転送に失敗しました。ソースサイトにホットリンク防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします(img-t4JNC7Bv-1615786729416)(C:\ Users \ VSUS \ Desktop \ Notes \ Multithread \ img \ 16.png)]
分析:スレッドAとスレッドBが開始されると、スレッドAは最初にCPUをプリエンプトして現在のオブジェクトのロックを取得し、HasSelfPrivateNumのaddIを実行し、num = 100を設定してから、Thread.sleep(2000);、スレッドAを実行します。休止状態では、スレッドBはこの時点でaddIを実行しようとしていますが、オブジェクトのロックを取得しておらず、待機することしかできません。スレッドAがaddIメソッドの実行を終了すると、ロックが解放され、スレッドBが開始します。ロックを取得してaddIを実行するには
1.複数のオブジェクトと複数のロックを同期することはできません
上記のrunメソッドを次のように変更します。
public class Run {
public static void main(String[] args) {
HasSelfPrivateNum numRef1 = new HasSelfPrivateNum();
HasSelfPrivateNum numRef2 = new HasSelfPrivateNum();
new ThreadA(numRef1).start();
new ThreadB(numRef2).start();
}
}
操作の結果は次のとおりです。
[外部リンクの画像転送に失敗しました。ソースサイトにホットリンク防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします(img-rjXJIVhV-1615786729419)(C:\ Users \ VSUS \ Desktop \ Notes \ Multithreading \ img \ 17.png)]
同期されたロックではなく、操作の順序が交差していることがわかります。同期されたロックはコードではなくオブジェクトです。複数のスレッドが複数のオブジェクトにアクセスすると、JVMは複数のロックを作成するため、実行結果は非同期になります。
2.同期ロック再突入
再入可能ロック:独自の内部ロックを再度取得できます。たとえば、スレッドがオブジェクトのロックを取得した場合、オブジェクトロックが解放されていなければ、オブジェクトはオブジェクトロックを取得できます。
public class Service {
synchronized public void service1(){
try{
System.out.println("begin service1 threadName=" + Thread.currentThread().getName() + " time"
+ System.currentTimeMillis()
);
System.out.println("service1..");
Thread.sleep(2000);
System.out.println("end service1 threadName=" + Thread.currentThread().getName() + " time"
+ System.currentTimeMillis()
);
service2();
}catch (InterruptedException e){
e.printStackTrace();
}
}
synchronized public void service2(){
try{
System.out.println("begin service2 threadName=" + Thread.currentThread().getName() + " time"
+ System.currentTimeMillis()
);
System.out.println("service2...");
Thread.sleep(2000);
System.out.println("end service2 threadName=" + Thread.currentThread().getName() + " time"
+ System.currentTimeMillis()
);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
public class MyThread2 extends Thread{
private Service service;
public MyThread2(Service service) {
this.service = service;
}
@Override
public void run() {
service.service2();
}
}
public class Run {
public static void main(String[] args) {
Service service = new Service();
new MyThread1(service).start();
}
}
Serviceクラスでは、すべて同期によって変更されるメソッドservice1とservice2がそれぞれ作成されます。Service1はservice2を呼び出し、MyThread1はRunクラスのメインによって開始されるスレッドを作成します。証明される結論は、スレッドがオブジェクトのロックを再度取得できます。つまり、service2メソッドが実行されます。
[外部リンクの画像転送に失敗しました。ソースサイトにアンチホットリンクメカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします(img-RCbvcsSM-1615786729425)(C:\ Users \ VSUS \ Desktop \ Notes \ Multithread \ img \ 18.png)]
ただし、スレッドがロックを取得した場合でも、スレッドMyThread2を再度作成することを証明するにはこれだけでは不十分です。
[外部リンクの画像転送に失敗しました。ソースサイトにホットリンク防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします(img-rDKGpUZE-1615786729426)(C:\ Users \ VSUS \ Desktop \ Notes \ Multithread \ img \ 19.png)]
スレッド1はservice2メソッドの実行時にオブジェクトのロックを保持しているため、スレッド1とスレッド2は非同期で実行され、スレッド2は待機することしかできないことがわかります。
したがって、スレッドが同期ロックを取得した場合、他のスレッドはロックによってロックされたメソッドを再度呼び出すことができないことが上記からわかります。同期によってロックされていないメソッドを呼び出すことはできますか?上記のコードを変更します。service2の同期メソッドを削除し、service2のsleepメソッドを削除します。スレッド1はservice1メソッドを呼び出し、スレッド2はservice2メソッドを呼び出します。
public class Service {
synchronized public void service1(){
......
}
public void service2(){
......
//Thread.sleep(2000);
......
);
}
}
public class MyThread1 extends Thread{
private Service service;
public MyThread1(Service service) {
this.service = service;
}
@Override
public void run() {
service.service1();
}
}
public class MyThread2 extends Thread{
private Service service;
public MyThread2(Service service) {
this.service = service;
}
@Override
public void run() {
service.service2();
}
}
[外部リンクの画像転送に失敗しました。ソースサイトにホットリンク防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします(img-m2XKTp5X-1615786729428)(C:\ Users \ VSUS \ Desktop \ notes \ multithreading \ img \ 20.png)]
スレッド1とスレッド2は非同期で実行されます。つまり、スレッド2は同期ロックによってロックされていないメソッドを呼び出すことができます。
上記の実行プロセスの分析:スレッド1が最初に開始してロックを取得し、service1メソッドの呼び出しを開始します。sleepメソッドに遭遇すると、スリープ状態になります。スレッド2はCPUによってタイムスライスに割り当てられ、 service2が同期されていないため、service2メソッドの呼び出しを開始します。変更により、スレッド2はservice2メソッドを呼び出し、スレッド2がservice2メソッドの実行を終了するまで待機します。スレッド1がウェイクアップした後、 service1メソッドで終了です!
重要な結論:
Aスレッドがsynchronizedキーワードを使用してanyObjectオブジェクトのXメソッドを呼び出すと、AスレッドはXメソッドが配置されているオブジェクトのロックを取得するため、他のスレッドはAスレッドが完了するのを待ってからXメソッドを呼び出す必要があります。また、Bスレッドがステートメントを呼び出す場合synchronizedキーワードを使用する非Xメソッドは、AスレッドがXメソッドを実行するのを待機する必要があります。つまり、ロックが解放された後に呼び出すことができます。
注:親子クラスの継承関係がある場合、子クラスは「再入可能ロック」を介して親クラスの同期メソッドを呼び出すことができますが、同期は継承されません。
3つの同期同期ステートメントブロック
キーワードsynchronizedを使用してメソッドを宣言すると、場合によっては欠点が発生します。たとえば、Aスレッドが同期メソッドを呼び出して長期タスクを実行し、次にBスレッドが長時間待機する必要があり、次に同期を使用する一部をロックするだけで非スレッドセーフの問題が発生する可能性がありますスレッドセーフの問題のコードブロックはある程度の効率を向上させます。さらに、オブジェクト自体は同期メソッドを使用してロックされます。これにより、オブジェクトインスタンスが1つしかない場合は、非同期で実行され、同期されたステートメントブロックを使用すると、スレッドセーフの問題なしに同期的に実行できます
public class Task {
public void doLongTime(){
for (int i = 1; i <= 100; i++) {
System.out.println("noSynchronized threadName = " + Thread.currentThread().getName() + " i=" + i);
}
synchronized (this){
for (int i = 1; i <= 100; i++) {
System.out.println("Synchronized threadName = " + Thread.currentThread().getName() + " i=" + i);
}
}
}
}
public class MyThread implements Runnable {
private Task task;
public MyThread(Task task) {
this.task = task;
}
@Override
public void run() {
task.doLongTime();
}
}
public class Run {
public static void main(String[] args) {
Task task = new Task();
Thread t1 = new Thread(new MyThread(task));
t1.start();
Thread t2 = new Thread(new MyThread(task));
t2.start();
}
}
長期タスクをシミュレートするタスククラスを作成し、2つのプロセスを作成します。起動後、同期で囲まれていないコードブロックが非同期で実行され、同期で囲まれたコードが同期で実行されることがわかります。
[外部リンクの画像転送に失敗しました。ソースサイトにホットリンク防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします(img-wI16ulzi-1615786729431)(C:\ Users \ VSUS \ Desktop \ Notes \ Multithread \ img \ 21.png)]
[外部リンク画像の転送に失敗しました。ソースサイトにヒル防止リンクメカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします(img-jKP5TkS0-1615786729432)(C:\ Users \ VSUS \ Desktop \ Notes \ Multithread \ img \ 22.png)]
同期(this)を使用して現在のオブジェクトをロックします
synchorinzed(x)、ここでxは任意のオブジェクトであり、3つの結論を引き出すことができます
- 複数のスレッドがsynchorinzed(x)同期コードブロックを同時に実行すると、同期効果が示されます
- 他のスレッドがxオブジェクトでsynchorinzed同期メソッドを実行すると、同期効果も示されます。
リビングコードブロックは非同期で実行され、synchronizedで囲まれたコードは同期で実行されます。
[外部リンク画像が転送されています...(img-wI16ulzi-1615786729431)]
[外部リンク画像が転送されています...(img-jKP5TkS0-1615786729432)]
同期(this)を使用して現在のオブジェクトをロックします
synchorinzed(x)、ここでxは任意のオブジェクトであり、3つの結論を引き出すことができます
- 複数のスレッドがsynchorinzed(x)同期コードブロックを同時に実行すると、同期効果が示されます
- 他のスレッドがxオブジェクトでsynchorinzed同期メソッドを実行すると、それも同期されます。
- 他のスレッドがxオブジェクトメソッドでsynchorinzed(this)コードブロックを実行すると、同期効果もあります。