1.Javaでのスレッド同期
マルチスレッド環境では、複数のスレッドが同じリソースを変更しようとする場合があります。もちろん、スレッドを誤って管理すると、一貫性の問題が発生する可能性があります。スレッドのライフサイクルに関しては、このライフサイクルを制御する方法はたくさんあります。ここでwait()とnotify()に注目してください。
Object.wait()はスレッドを一時停止します
Object.notify()はスレッドをウェイクアップします
1. wait()メソッド
wait()を呼び出すと、現在のスレッドは、別のスレッドが同じオブジェクトでnotify( )またはnotifyAll()を呼び出すまで待機します。これを行うには、現在のスレッドがオブジェクトのモニターを所有している必要があります。Javadocsによると、これは次の方法で発生する可能性があります。
(1)特定のオブジェクトに対して同期インスタンスメソッドを実行する場合
(2)指定されたオブジェクトで同期ブロックの本体を実行したとき
(3)クラスタイプのオブジェクトに対して同期された静的メソッドを実行する
一度に1つのアクティブなスレッドのみがオブジェクトのモニターを所有できることに注意してください。
(1)待つ()
wait()メソッドは、別のスレッドがこのオブジェクトに対してnotify( )またはnotifyAll()を呼び出すまで、現在のスレッドを無期限に待機さ せます。
(2)待つ(長いタイムアウト)
この方法を使用すると、スレッドが自動的にウェイクアップされるまでのタイムアウトを指定できます。notify()またはnotifyAll()を使用して、タイムアウトに達する前にスレッドをウェイクアップできます。
wait(0)を呼び出すことは、wait()を呼び出すことと同じであることに注意してください。
(3)待機(長いタイムアウト、int nanos)
同じ機能を提供する別の方法を次に示します。より高い精度を提供できます。
2、notify()およびnotifyAll()
notify()メソッドを使用して、オブジェクトのモニターへのアクセスを待機しているスレッドをウェイクアップします。
待機中のスレッドに通知する方法は2つあります。
(1)通知()
(wait()メソッドのいずれかを使用して)このオブジェクトのモニターで待機しているすべてのスレッドについて、notify()メソッドはそれらのいずれかに任意にウェイクアップするように通知します。どのスレッドをウェイクアップするかは、決定論的ではなく、実装に依存します。
notify()は単一のランダムスレッドをウェイクアップするため、これを使用して、スレッドが同様のタスクを実行しているミューテックスロックを実装できます。ただし、ほとんどの場合、notifyAll()を実装する方が現実的です。
(2)notifyAll()
このメソッドは、このオブジェクトのモニターで待機しているすべてのスレッドを単にウェイクアップします。目覚めたスレッドは、他のスレッドと同じように、通常の方法で完了します。
3.送受信者の同期の問題
(1)データクラス
基本を理解したので、wait()メソッドとnotify()メソッドを使用してそれらの間の同期を設定する単純なSender-Receiverアプリケーションを見てみましょう。
送信者は受信者にパケットを送信する必要があります。
受信者は、送信者が送信を終了するまでパケットを処理できません。
同様に、受信者が前のパケットをすでに処理していない限り、送信者は別のパケットを送信しようとしないでください。
送信者から受信者に送信されるパケットを含むデータクラスを作成することから始めましょう。wait()とnotifyAll()を使用して、それらの間の同期を設定します。
public class Data {
private String packet;
// True if receiver should wait
// False if sender should wait
private boolean transfer = true;
public synchronized String receive() {
while (transfer) {
try {
wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.out.println("Thread Interrupted");
}
}
transfer = true;
String returnPacket = packet;
notifyAll();
return returnPacket;
}
public synchronized void send(String packet) {
while (!transfer) {
try {
wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.out.println("Thread Interrupted");
}
}
transfer = false;
this.packet = packet;
notifyAll();
}
}
(1)パケット変数は、ネットワークを介して送信されるデータを表します。
(2)送信者と受信者が同期に使用する 転送変数。
この変数がtrueの場合、受信者は送信者がメッセージを送信するのを待つ必要があります。
falseの場合、送信者は受信者がメッセージを受信するのを待つ必要があります。
(3)送信者はsend ()メソッドを使用してデータを受信者に送信します
転送がfalseの 場合、そのスレッドでwait()を呼び出して待機します。
ただし、trueの場合は、状態を切り替えてメッセージを設定し、notifyAll()を呼び出して他のスレッドをウェイクアップし、メジャーイベントが発生したことを指定して、実行を続行できるかどうかを確認できます。
(4)レシーバーはreceive()メソッドを使用します
転送は、送信者によってfalseに設定されている場合にのみ続行されます。それ以外の場合は、このスレッドでwait()を呼び出します。
条件が満たされると、状態を切り替え、待機中のすべてのスレッドにウェイクアップを通知し、受信したパケットを返します。
(2)送信者クラス
public class Sender implements Runnable {
private Data data;
public Sender(Data data) {
this.data = data;
}
public void run() {
String packets[] = {
"First packet",
"Second packet",
"Third packet",
"Fourth packet",
"End"
};
for (String packet : packets) {
data.send(packet);
// Thread.sleep() to mimic heavy server-side processing
try {
Thread.sleep(ThreadLocalRandom.current().nextInt(1000, 5000));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
Log.error("Thread interrupted", e);
}
}
}
}
パケット[]配列でネットワークを介して送信されるいくつかのランダムなパケットを作成します。
パケットごとに、send()が呼び出されて送信されます。
次に、ランダムな間隔でThread.sleep()を呼び出して、集中的なサーバー側の処理をシミュレートします。
(3)レシーバークラス
Load.receive()は、最後のパケットを取得するまでループで呼び出されます。
public class Receiver implements Runnable {
private Data load;
public Receiver(Data load) {
this.load = load;
}
public void run() {
for(String receivedMessage = load.receive();
!"End".equals(receivedMessage);
receivedMessage = load.receive()) {
System.out.println(receivedMessage);
// ...
try {
Thread.sleep(ThreadLocalRandom.current().nextInt(1000, 5000));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
Log.error("Thread interrupted", e);
}
}
}
}
(4)呼び出しと出力
public static void main(String[] args) {
Data data = new Data();
Thread sender = new Thread(new Sender(data));
Thread receiver = new Thread(new Receiver(data));
sender.start();
receiver.start();
}
try {
Thread.sleep(30000);
} catch (InterruptedException e) {
e.printStackTrace();
}
次の出力を受け取ります
最初のパケット
2番目のパケット
3番目のパケット
4番目のパケット
2. suspend()およびresume()
スレッドを一時停止すると、スレッドも実行を再開できます。Javaマルチスレッドでは、suspend()メソッドを使用してスレッドを一時停止し、resume()メソッドを使用してスレッドの実行を再開できます。
ただし、これら2つのメソッドは無効および期限切れとしてマークされているため、スレッドを一時停止および再開するプロセスを実装する場合は、wait()、notify()、またはnotifyAll()メソッドを使用できます。
3.LockSupportクラスを使用します
suspend()メソッドとresume()メソッドは期限切れになり、無効になります。同じ関数を実行する場合は、代わりにJDK並行パッケージで提供されるLockSupportクラスを使用することもでき、効果は同じです。
1.LockSupportの一般的なソースコード
パークとアンパークを使用して、スレッドをブロックおよびウェイクアップします。パークとアンパークの最下層は、システム層(C言語)メソッドpthread_mutexおよびpthread_condによって実装されます。pthread_cond_wait関数はスレッドをブロックできます。この前に、pthread_mutexを取得する必要があります。 pthread_cond_signal関数を介してスレッドをウェイクアップします。
// LockSupport
public static void park(Object blocker) {
Thread t = Thread.currentThread();
// blocker在什么对象上进行的阻塞操作
setBlocker(t, blocker);
UNSAFE.park(false, 0L);
setBlocker(t, null);
}
public static void parkNanos(Object blocker, long nanos) {
if (nanos > 0) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
// 超时阻塞
UNSAFE.park(false, nanos);
setBlocker(t, null);
}
}
public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}
2.デモインスタンス1
runメソッドでparkメソッドを呼び出すスレッドを作成します。
package com.algorithm.demo.thread;
import java.util.concurrent.locks.LockSupport;
public class LockSupportDemo extends Thread{
@Override
public void run()
{
System.out.println("begin" + System.currentTimeMillis());
LockSupport.park();
System.out.println("end" + System.currentTimeMillis());
}
}
スレッドがインスタンス化され、メインスレッドがスリープしてから、unparkメソッドが呼び出されます。
@Test
void test_locksupport()
{
LockSupportDemo demo = new LockSupportDemo();
demo.start();
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
LockSupport.unpark(demo);
}
出力は次のとおりです
begin1647607496657
end1647607497661
park()メソッドの機能はスレッドを一時停止することであり、unpark()メソッドの機能はスレッドの実行を再開することです。
3.デモインスタンス2
unpark()メソッドが最初に実行され、次にpark()メソッドが実行される場合、park()メソッドは中断されません。
package com.algorithm.demo.thread;
import java.util.concurrent.locks.LockSupport;
public class LockSupportDemo extends Thread{
@Override
public void run()
{
System.out.println("begin" + System.currentTimeMillis());
LockSupport.park();
System.out.println("end" + System.currentTimeMillis());
}
}
スレッドがインスタンス化され、メインスレッドがunparkメソッドを呼び出します。
@Test
void test_locksupport()
{
LockSupportDemo demo = new LockSupportDemo();
demo.start();
LockSupport.unpark(demo);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
出力は次のとおりです
begin1647607590322
end1647607590322
unpark()メソッドが最初に実行され、次にpark()メソッドが実行された場合、park()メソッドが一時停止の効果を持たないことを示します。