Mode suspension surveillée: en attente du mécanisme de réveil

Sujet: Mode de suspension surveillée: en attente de la spécification du mécanisme de réveil

Il y a peu de temps, un collègue Xiao Hui a rencontré un problème dans son travail. Il a développé un projet Web: une version Web du navigateur de fichiers, à travers laquelle les utilisateurs peuvent visualiser les répertoires et les fichiers sur le serveur dans le navigateur. Ce projet repose sur le service d'exploration de fichiers fourni par le service d'exploitation et de maintenance, et ce service d'exploration de fichiers prend uniquement en charge l'accès à la file d'attente de messages (MQ). La mise en file d'attente des messages est largement utilisée dans les grandes sociétés Internet, principalement utilisée pour les pics de trafic et le découplage du système. Dans ce mode d'accès, les deux opérations d'envoi de messages et de consommation de résultats sont asynchrones, vous pouvez vous référer au diagramme suivant pour comprendre.
Insérez la description de l'image ici
Dans le projet Web de Xiaohui, une demande envoyée par un utilisateur via un navigateur est convertie en un message asynchrone et envoyée à MQ. Une fois que MQ a renvoyé le résultat, le résultat est renvoyé au navigateur. La question de Xiaohui est la suivante: le thread qui envoie des messages à MQ est le thread T1 qui traite la demande Web, mais le thread qui consomme le résultat MQ n'est pas le thread T1. Comment le thread T1 attend-il le résultat de retour du MQ? Afin de faciliter votre compréhension de ce scénario, il est codé, l'exemple de code est le suivant.

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 = ...;
}

Semblable au problème de l'asynchrone au synchrone, aujourd'hui, nous parlerons de ce problème avec attention pour vous en informer et vous pouvez concevoir vous-même une solution si vous rencontrez des problèmes similaires.

Suspension gardée 模式

Les problèmes rencontrés par le petit gris ci-dessus sont partout dans le monde réel, mais nous les avons ignorés accidentellement.Par exemple, L'équipe du projet va sortir pour le dîner, nous avons réservé une chambre privée à l'avance, puis nous nous sommes précipités vers le passé, après cela, le responsable du hall a jeté un coup d'œil à la salle privée et a constaté que le serveur faisait ses valises, il nous disait: "Le préposé à la chambre privée que vous avez réservé fait ses valises Veuillez patienter un instant. "Après un moment, le responsable du hall a constaté que la salle privée avait été remplie, il nous a donc immédiatement emmenés dans la salle privée pour le dîner.

Le processus que nous attendons pour que le package soit terminé est essentiellement le même que l'attente de MQ pour renvoyer le message rencontré par Xiaohui. Ils attendent tous qu'une condition soit remplie: la restauration doit attendre que le package soit nettoyé et le programme de Xiaohui doit attendre que le MQ renvoie le message.

Voyons donc comment résoudre de tels problèmes dans le monde réel? Dans le monde réel, le rôle du responsable du lobby est très important, que nous attendions ou non est entièrement coordonné par lui. Par analogie, je pense que vous devez également avoir des idées: dans notre programme, nous avons également besoin d'un tel responsable de lobby. En effet, comment le responsable du lobby dans le monde de la programmation devrait-il le concevoir? En fait, les prédécesseurs du plan de conception l'ont déjà compris, et il a été résumé en un modèle de conception: Suspension sécurisée. La soi-disant suspension gardée est littéralement «suspendue de manière protectrice» . Voyons ensuite comment le mode Suspension surveillée simule la suspension de protection du responsable du hall.

La figure suivante est le diagramme de structure du mode Suspension suspendue. C'est très simple. Un objet GuardedObject a une variable membre - un objet protégé et deux méthodes get(Predicate<T> p)et onChanged(T obj)méthodes membres. Parmi eux, l'objet GuardedObject est le gestionnaire du hall que nous avons mentionné plus tôt, et l'objet protégé est la salle privée du restaurant; la méthode get () de l'objet protégé correspond à notre salle à manger. La condition préalable à la salle à manger est que la salle privée a été emballée , le paramètre p Il est utilisé pour décrire cette condition préalable; la méthode onChanged () de l'objet protégé correspond à ce que le serveur a emballé la salle de package. Grâce à la méthode onChanged (), un événement peut être déclenché et cet événement peut souvent changer le résultat du calcul de la condition préalable p. Dans l'image ci-dessous, le fil vert à gauche est le client qui a besoin de manger et le fil bleu à droite est le serveur qui fait le sac.

Insérez la description de l'image ici
L'implémentation interne de GuardedObject est très simple et constitue une utilisation classique du canal . Vous pouvez vous référer à l'exemple de code suivant. Le noyau est le suivant: la méthode get () attend via la méthode wait () de la variable de condition et la méthode onChanged () transmet le signalAll de la variable de condition () Méthode pour réaliser la fonction de réveil. La logique est très simple, donc je ne vais pas entrer dans les détails ici.

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();
    }
  }
}

Mode de suspension surveillée étendue

Nous avons présenté ci-dessus le modèle Guarded Suspension et son implémentation. Ce modèle peut simuler le rôle du responsable du lobby dans le monde réel. Voyons maintenant si ce "responsable du lobby" peut résoudre les problèmes rencontrés par les petits camarades gris.

GuardedObject en mode Guarded Suspension a deux méthodes principales, l'une est la méthode get () et l'autre la méthode onChanged (). Évidemment, dans la méthode handleWebReq () de traitement des requêtes Web, vous pouvez appeler la méthode get () de GuardedObject pour attendre; dans la méthode de consommation onMessage () des messages MQ, vous pouvez appeler la méthode onChanged () de GuardedObject pour vous réveiller.

//处理浏览器发来的请求
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);
}

Cependant, il y aura un problème lors de l'implémentation. Dans handleWebReq (), une instance de l'objet GuardedObject go est créée et sa méthode get () est appelée pour attendre le résultat. Dans la méthode onMessage (), comment trouver un objet GuardedObject correspondant? ? Ce processus est similaire au serveur qui dit au responsable du hall que la salle privée a été emballée et comment le responsable du lobby peut trouver la personne à manger en fonction de la chambre privée. Dans le monde réel, le responsable du hall a un diagramme de la relation entre la salle privée et les convives, afin que le responsable du lobby puisse trouver les convives immédiatement après que le serveur a fini de parler.

Nous pouvons nous référer à la méthode du responsable du hall pour identifier la personne qui mange, pour étendre le mode Suspension surveillée, afin qu'il puisse facilement résoudre le problème des petits camarades de classe gris. Dans le petit programme gris, chaque message envoyé à MQ a un identifiant d'attribut unique, afin que nous puissions maintenir la relation entre un identifiant de message MQ et une instance d'objet GuardedObject. Cette relation peut être comparée à la salle privée maintenue dans le cerveau du responsable du lobby Relation avec la personne qui dîne.

Avec cette relation, voyons comment y parvenir. L'exemple de code suivant est une implémentation du mode de suspension Guarded étendu. Le GuardedObject étendu conserve une carte, dont la clé est l'ID du message MQ, et la valeur est l'instance d'objet GuardedObject, et ajoute des méthodes statiques create () et fireEvent (); create La méthode () est utilisée pour créer une instance d'objet GuardedObject et l'ajouter à la carte en fonction de la valeur de clé, tandis que la méthode fireEvent () est la logique du gestionnaire de lobby simulé pour trouver les convives en fonction de la salle privée.

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();
    }
  }
}

De cette façon, il est très simple d'utiliser l'extension GuardedObject pour résoudre le problème des camarades de classe de Xiaohui. Le code spécifique est illustré ci-dessous.

//处理浏览器发来的请求
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);
}

Résumé

Le mode Suspension surveillée est essentiellement une implémentation du mécanisme d'attente de réveil , mais le mode Suspension surveillée le normalise. L'avantage de la normalisation est que vous n'avez pas besoin de réfléchir à la façon de l'implémenter, vous n'avez pas à vous soucier de la compréhensibilité du programme d'implémentation et vous pouvez éviter d'écrire un bogue accidentellement. Mais le mode Guarded Suspension doit souvent être étendu lors de la résolution de problèmes pratiques. Il existe de nombreuses façons de se développer. Cet article améliore directement la fonction de GuardedObject. La classe defaultFuture dans Dubbo est également utilisée de cette façon. Vous pouvez En revanche, je pense que le principe de mise en œuvre de DefaultFuture sera mieux compris. Bien sûr, vous pouvez également créer de nouvelles classes pour étendre le modèle de suspension gardée.

Le mode de suspension surveillée est également souvent appelé mode d'attente surveillée, mode de verrouillage rotatif (en raison de la boucle while à attendre), ces noms sont très vifs, mais il a également un nom officieux plus vif :Version multi-thread de if. Dans le scénario à thread unique, l'instruction if n'a pas besoin d'attendre, car sous la condition d'un seul thread, si ce thread est bloqué, il n'y a pas d'autres threads actifs, ce qui signifie que le résultat de la condition if ne changera pas. . Mais dans un scénario à plusieurs threads, l'attente devient significative. Dans ce scénario, le résultat de la condition if peut changer. Par conséquent, il serait plus simple de comprendre ce modèle avec une "version multithread de if".

Démo

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;
    }
}
138 articles originaux publiés · J'aime 3 · Visiteur 7206

Je suppose que tu aimes

Origine blog.csdn.net/weixin_43719015/article/details/105692735
conseillé
Classement