トピック:ガードされたサスペンションモード:ウェイクアップメカニズムの指定を待機しています
少し前に、同僚のXiao Huiが作業中に問題に遭遇し、Webプロジェクトを開発しました。これは、ユーザーがブラウザでサーバー上のディレクトリとファイルを表示できるファイルブラウザのWebバージョンです。このプロジェクトは、運用保守部門が提供するファイル参照サービスに依存しており、このファイル参照サービスはメッセージキュー(MQ)アクセスのみをサポートしています。メッセージキューイングは、大規模なインターネット企業で広く使用されており、主にトラフィックのピークとシステムのデカップリングに使用されます。このアクセスモードでは、メッセージの送信と結果の消費の2つの操作は非同期です。理解するには、次の図を参照してください。
XiaohuiのWebプロジェクトでは、ユーザーがブラウザーを介して送信した要求は、非同期メッセージに変換されてMQに送信されます。MQが結果を返した後、結果がブラウザーに返されます。Xiaohuiの質問は、メッセージをMQに送信するスレッドは、Webリクエストを処理するスレッドT1ですが、MQ結果を消費するスレッドはスレッドT1ではありません。スレッドT1は、MQの戻り結果をどのように待機するのですか?このシナリオの理解を容易にするために、コード化されています。サンプルコードは次のとおりです。
class Message{
String id;
String content;
}
//该方法可以发送消息
void send(Message msg){
//省略相关代码
}
//MQ消息返回后会调用该方法
//该方法的执行线程不同于发送消息的线程
void onMessage(Message msg){
//省略相关代码
}
//处理浏览器发来的请求
Respond handleWebReq(){
//创建一消息
Message msg1 = new Message("1","{...}");
//发送消息
send(msg1);
//如何等待MQ返回的消息呢?傻傻的等待还机智的等待呢??
String result = ...;
}
非同期から同期への問題と同様に、今日はこの問題について注意深く説明し、その理由を説明します。同様の問題が発生した場合は、自分でソリューションを設計できます。
ガードされたサスペンション
上記の小さな灰色が直面する問題は現実世界のいたるところにありますが、私たちはそれらを誤って無視しました。たとえば、プロジェクトチームは夕食に出かける予定です。私たちは事前に個室を予約し、それから過去に急いで行きました。その後、ロビーマネージャーは個室をちらっと見て、ウェイターが荷造りしているのを見つけました。「あなたが予約した個室アテンダントが荷造りしている、しばらくお待ちください。「しばらくすると、ロビーのマネージャーは個室が満員になっているのを見つけたので、すぐに夕食のために個室に連れて行ってくれました。
パッケージが完了するのを待つプロセスは、基本的に、Xiaohuiが遭遇したメッセージをMQが返すのを待つプロセスと同じです。それらはすべて、条件が満たされるのを待っています。ダイニングはパッケージがクリーンアップされるのを待つ必要があり、XiaohuiのプログラムはMQがメッセージを返すのを待つ必要があります。
では、このような問題を現実の世界で解決する方法を見てみましょう。実社会では、ロビーマネージャーの役割は非常に重要であり、待機するかどうかは完全に彼によって調整されます。類推すると、あなたもアイデアを持っている必要があると思います。私たちのプログラムでは、そのようなロビーマネージャーも必要です。実際、プログラミングの世界のロビーマネージャーはそれをどのように設計すればよいでしょうか。実際、設計計画の前任者はすでにそれを手に入れており、それは設計パターンに要約されています:保護されたサスペンション。いわゆるGuarded Suspensionは、文字通り「保護的に停止」されています。次に、Guarded Suspensionモードがロビーマネージャーの保護停止をどのようにシミュレートするかを見てみましょう。
次の図は、ガードされたサスペンションモードの構造図です。オブジェクトGuardedObjectには、メンバー変数(保護オブジェクトと2つのメンバーメソッドget(Predicate<T> p)
およびonChanged(T obj)
メソッド)があります。その中で、オブジェクトGuardedObjectは前述のロビーマネージャーであり、保護オブジェクトはレストランのプライベートルームです。保護オブジェクトのget()メソッドはダイニングに対応しています。ダイニングの前提条件は、プライベートルームがパッキングされていること、パラメーターpです。これは、この前提条件を説明するために使用されます。保護オブジェクトのonChanged()メソッドは、ウェイターがパッケージルームを詰め込んだことに対応します。onChanged()メソッドを介してイベントを発生させることができ、このイベントは、前提条件pの計算結果を変更することがよくあります。下の写真では、左側の緑色の糸が食事をする必要のある顧客で、右側の青色の糸が袋を詰めるウェイターです。
GuardedObjectの内部実装は非常にシンプルで、パイプの典型的な使用法です。次のサンプルコードを参照できます。コアは、条件変数のawait()メソッドを介してget()メソッドが待機し、onChanged()メソッドが条件変数のsignalAllを渡すことです。 ()ウェイクアップ機能を実現する方法。ロジックは非常に単純なので、ここでは詳しく説明しません。
class GuardedObject<T>{
//受保护的对象
T obj;
final Lock lock = new ReentrantLock();
final Condition done = lock.newCondition();
final int timeout=1;
//获取受保护对象
T get(Predicate<T> p) {
lock.lock();
try {
//MESA管程推荐写法
while(!p.test(obj)){
done.await(timeout, TimeUnit.SECONDS);
}
}catch(InterruptedException e){
throw new RuntimeException(e);
}finally{
lock.unlock();
}
//返回非空的受保护对象
return obj;
}
//事件通知方法
void onChanged(T obj) {
lock.lock();
try {
this.obj = obj;
done.signalAll();
} finally {
lock.unlock();
}
}
}
拡張ガードサスペンションモード
上記では、Guarded Suspensionモデルとその実装を紹介しました。このモデルは、実世界でのロビーマネージャーの役割をシミュレートできます。次に、この「ロビーマネージャー」が小さな灰色のクラスメートが遭遇する問題を解決できるかどうかを見てみましょう。
Guarded SuspensionモードのGuardedObjectには2つのコアメソッドがあります。1つはget()メソッドで、もう1つはonChanged()メソッドです。明らかに、Webリクエストを処理するhandleWebReq()メソッドでは、GuardedObjectのget()メソッドを呼び出して待機できます。MQメッセージのonMessage()消費メソッドでは、GuardedObjectのonChanged()メソッドを呼び出してウェイクアップできます。
//处理浏览器发来的请求
Respond handleWebReq(){
//创建一消息
Message msg1 = new Message("1","{...}");
//发送消息
send(msg1);
//利用GuardedObject实现等待
GuardedObject<Message> go =new GuardObjec<>();
Message r = go.get(t->t != null);
}
void onMessage(Message msg){
//如何找到匹配的go?
GuardedObject<Message> go=???
go.onChanged(msg);
}
ただし、実装中に問題が発生します。handleWebReq()では、GuardedObjectオブジェクトgoのインスタンスが作成され、そのget()メソッドが呼び出されて結果を待機します。onMessage()メソッドでは、一致するGuardedObjectオブジェクトをどのように見つけることができますか? ?このプロセスは、ウェイターがロビーマネージャーに個室が満員になったことを伝え、ロビーマネージャーが個室に応じて食べる人を見つける方法と似ています。実世界では、ロビーマネージャーは個室とダイナーの関係図を持っているので、ウェイターが話し終わった直後にロビーマネージャーがダイナーを見つけることができます。
ロビーマネージャーのメソッドを参照して、食事をする人を特定し、ガード付きサスペンションモードを拡張して、小さな灰色のクラスメートの問題を簡単に解決できるようにします。小さな灰色のプログラムでは、MQに送信される各メッセージに一意の属性IDがあるため、MQメッセージIDとGuardedObjectオブジェクトインスタンスの間の関係を維持できます。この関係は、ロビーマネージャーの頭脳で維持されるプライベートルームと比較できます。食事をする人との関係。
この関係で、それを達成する方法を見てみましょう。次のサンプルコードは、拡張されたGuarded Suspensionモードの実装です。拡張されたGuardedObjectは、キーがMQメッセージIDで、値がGuardedObjectオブジェクトのインスタンスであるマップを保持し、静的メソッドcreate()およびfireEvent();を追加します。 ()メソッドは、GuardedObjectオブジェクトインスタンスを作成し、キー値に従ってマップに追加するために使用されます。一方、fireEvent()メソッドは、プライベートルームに応じてダイナーを見つけるためのシミュレートされたロビーマネージャーのロジックです。
class GuardedObject<T>{
//受保护的对象
T obj;
final Lock lock = new ReentrantLock();
final Condition done =lock.newCondition();
final int timeout=2;
//保存所有GuardedObject
final static Map<Object, GuardedObject> gos=new ConcurrentHashMap<>();
//静态方法创建GuardedObject
static <K> GuardedObject create(K key){
GuardedObject go = new GuardedObject();
gos.put(key, go);
return go;
}
static <K, T> void fireEvent(K key, T obj){
GuardedObject go=gos.remove(key);
if (go != null){
go.onChanged(obj);
}
}
//获取受保护对象
T get(Predicate<T> p) {
lock.lock();
try {
//MESA管程推荐写法
while(!p.test(obj)){
done.await(timeout, TimeUnit.SECONDS);
}
}catch(InterruptedException e){
throw new RuntimeException(e);
}finally{
lock.unlock();
}
//返回非空的受保护对象
return obj;
}
//事件通知方法
void onChanged(T obj) {
lock.lock();
try {
this.obj = obj;
done.signalAll();
} finally {
lock.unlock();
}
}
}
このように、Xiaohuiのクラスメートの問題を解決するために拡張されたGuardedObjectを使用するのは非常に簡単で、以下に特定のコードを示します。
//处理浏览器发来的请求
Respond handleWebReq(){
int id=序号生成器.get();
//创建一消息
Message msg1 = new Message(id,"{...}");
//创建GuardedObject实例
GuardedObject<Message> go= GuardedObject.create(id);
//发送消息
send(msg1);
//等待MQ消息
Message r = go.get(t->t != null);
}
void onMessage(Message msg){
//唤醒等待的线程
GuardedObject.fireEvent(msg.id, msg);
}
まとめ
ガードされた一時停止モードは、本質的にウェイクフォーウェイクメカニズムの実装ですが、ガードされた一時停止モードはそれを正規化します。正規化の利点は、それを実装する方法を考える必要がないこと、実装プログラムの理解可能性を心配する必要がないこと、そして誤ってバグを書くことを回避できることです。ただし、実際の問題を解決するには、Guarded Suspensionモードを拡張する必要があることがよくあります。拡張する方法はたくさんあります。この記事では、GuardedObjectの機能を直接強化しています。DubboのdefaultFutureクラスもこの方法で使用できます。対照的に、DefaultFutureの実装原理は、より完全に理解されると思います。もちろん、Guarded Suspensionパターンを拡張する新しいクラスを作成することもできます。
Guarded Suspensionモードは、Guarded Waitモード、Spin Lockモードとも呼ばれます(whileループが待機に使用されるため)。これらの名前は非常に鮮明ですが、非公式の名前もより鮮明です。ifのマルチスレッドバージョン。シングルスレッドシナリオでは、ifステートメントは待機する必要がありません。1つのスレッドのみの状態で、このスレッドがブロックされている場合、他のアクティブなスレッドはないため、if条件の結果は変化しません。 。しかし、マルチスレッドのシナリオでは、待機が意味を持つようになります。このシナリオでは、if条件の結果が変わる可能性があります。したがって、「ifのマルチスレッドバージョン」を使用してこのパターンを理解する方が簡単です。
デモ
1
public class SuspensionClient {
public static void main(String[] args) throws InterruptedException {
final RequestQueue queue = new RequestQueue();
new ClientThread(queue, "Alex").start();
ServerThread serverThread = new ServerThread(queue);
serverThread.start();
//serverThread.join();
Thread.sleep(10000);
serverThread.close();
}
}
2
public class RequestQueue {
private final LinkedList<Request> queue = new LinkedList<>();
public Request getRequest() {
synchronized (queue) {
while (queue.size() <= 0) {
try {
queue.wait();
} catch (InterruptedException e) {
return null;
}
}
Request request = queue.removeFirst();
return request;
}
}
public void putRequest(Request request) {
synchronized (queue) {
queue.addLast(request);
queue.notifyAll();
}
}
}
3
public class ClientThread extends Thread {
private final RequestQueue queue;
private final Random random;
private final String sendValue;
public ClientThread(RequestQueue queue, String sendValue) {
this.queue = queue;
this.sendValue = sendValue;
random = new Random(System.currentTimeMillis());
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("Client -> request " + sendValue);
//put就会唤醒等待的
queue.putRequest(new Request(sendValue));
try {
Thread.sleep(random.nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
4
public class ServerThread extends Thread {
private final RequestQueue queue;
private final Random random;
private volatile boolean closed = false;
public ServerThread(RequestQueue queue) {
this.queue = queue;
random = new Random(System.currentTimeMillis());
}
@Override
public void run() {
while (!closed) {
Request request = queue.getRequest();
if (null == request) {
System.out.println("Received the empty request.");
continue;
}
System.out.println("Server ->" + request.getValue());
try {
Thread.sleep(random.nextInt(1000));
} catch (InterruptedException e) {
return;
}
}
}
/**
* 这个方法作用是:巧妙的关闭了等待从RequestQueue获取Request对象的线程
* 类似于线程这样重资源的使用需要注意要释放资源
*/
public void close() {
this.closed = true;
this.interrupt();//queue.wait();这里可能一直阻塞了 this是当前线程
}
}
5
public class Request {
final private String value;
public Request(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}