3 votes

Modèle singleton par fil

Dans mon travail, je suis tombé sur un tel problème de conception :

  • J'ai besoin d'une instance d'un Manager classe par fil
  • Ces instances doivent être accessibles globalement, comme dans le modèle singleton, via une fonction statique.
  • Chaque thread peut avoir besoin d'initialiser son instance avec des arguments différents.
  • La durée de vie de ces instances devrait être contrôlable, il serait parfois avantageux de supprimer une instance et de permettre au GC de la collecter.

Les deux premiers points en feraient un "singleton par thread", si une telle chose existe.

Voici ce que j'ai trouvé (le code est simplifié, j'ai omis les contrôles de sécurité et autres) :

public class Manager {
  private final static ThreadLocal<Manager> local = new ThreadLocal<Manager>();

  private int x;
  Manager(int argument) { x = argument; }

  public static void start(int argument) { local.set(new Manager(argument); }
  public static void clean() { local.remove(); }

  private void doSomething1() { x++; .... }
  private int doSomething2() { if (--x == 0) clean(); ... }

  public static void function1() { local.get().doSomething1(); }
  public static int function2() { return local.get().doSomething2(); }
}

Comme vous pouvez le constater, la fonction de nettoyage peut également être appelée depuis les méthodes privées. Remarquez également que grâce à l'utilisation de fonctions statiques, la référence à l'instance n'est jamais perdue, de sorte que les instances assignées à différents threads ne seront pas mélangées.

Cela fonctionne très bien, mais j'ai ensuite une autre exigence :

  • Différents threads peuvent avoir besoin d'utiliser différentes implémentations de la classe Manager.

J'ai donc défini une interface :

public interface ManagerHandler {
  void method1();
  int method2();
}

Et modifié le Manager classe :

public class Manager {
  private final static ThreadLocal<ManagerHandler> local = new ThreadLocal<ManagerHandler>();

  public static void start(int argument) {
    ManagerHandler handler;
    // depending on the context initialize handler to whatever class it is necessary
    local.set(handler); 
  }
  public static void clean() { local.remove(); }

  public static void function1() { local.get().method1(); }
  public static int function2() { return local.get().method2(); }
}

Un exemple de mise en œuvre ressemblerait à ceci :

public class ExampleManagerImplementation implements ManagerHandler {
  private int x;
  public ExampleManagerImplementation(int argument) { x = argument; }
  public void method1() { x++; .... }
  public int method2() { if (--x == 0) Manager.clean(); ... }
}

La classe Manager fonctionne ici comme une façade, transférant tous les appels vers le gestionnaire approprié. Il y a un gros problème avec cette approche : Je dois définir toutes les fonctions à la fois dans la classe Manager et dans la classe ManagerHandler l'interface. Malheureusement, Manager ne peut pas implémenter ManagerHandler car elle comporte des fonctions statiques plutôt que des méthodes.

La question est la suivante : pouvez-vous imaginer un moyen meilleur/plus facile d'atteindre tous les objectifs que j'ai énumérés ci-dessus sans ce problème ?

0 votes

(Je suis obligée de dire que cela réunit tant de mauvaises choses. Un "Manager" est généralement un mauvais signe. Un "Manager" sans aucune indication de ce qu'il gère dans le nom. Une référence statique à un objet effectivement mutable. Une référence statique mutable. Un champ mutable non privé. Locaux de fils (Thread locals).

0 votes

Le code est simplifié. Le vrai nom de la classe n'est pas simplement 'Manager', bien sûr il indique de quoi il s'agit ;) Je peux ajouter le modificateur d'accès privé et final si vous le souhaitez. Mais qu'y a-t-il de mal à utiliser des locals de threads et une référence statique à un objet mutable (qui est thread-safe) ?

1voto

dm3 Points 1121

Il n'y a pas grand-chose que vous puissiez faire, car vous devez essentiellement faire passer les méthodes de l'interface par des méthodes statiques. Je n'ai trouvé que deux façons différentes d'obtenir la même fonctionnalité :

  1. Si vous utilisez un framework DI, vous pouvez vous débarrasser de l'élément statique Manager et utiliser une implémentation injectée de ManagerHandler qui contiendra le ThreadLocal .
  2. Générer (comme dans "génération de bytecode") le code statique de l'utilisateur. ManagerAccess en utilisant les méthodes trouvées dans la classe ManagerHandler interface.

Personnellement, je ne penserais pas à avoir la statique ManagerAccess (qui contient la classe ThreadLocal ) comme un problème de conception sérieux. Du moins, tant qu'il s'en tient à son propre ensemble de responsabilités (accès aux instances thread-scoped et appels par procuration) et ne s'aventure pas ailleurs.

0 votes

Je pense qu'il n'y a plus rien à dire sur ce sujet. Je vous remercie pour votre réponse.

1voto

Nick Points 6732

Si vous optez pour ce modèle, est-il nécessaire de Manager pour cacher totalement ManagerHandler ou pourriez-vous l'exposer pour ne pas avoir à déléguer chaque méthode ?

class Manager {
    public static ManagerHandler getHandler() { return local.get(); }
}

0 votes

J'ai également envisagé cette solution, mais je préférerais qu'elle soit cachée, de sorte que les références aux instances ne soient pas divulguées et que chaque thread ne puisse accéder qu'au bon objet.

-1voto

L'astuce pour créer un singleton par classe de thread est d'utiliser ThreadStatic sur votre champ statique privé _current, ce qui lui confère une portée par thread. De cette façon, le champ _current sera stocké dans la mémoire du thread qui n'est pas accessible aux autres threads et qui n'est pas une mémoire partagée de l'AppDomain. Ainsi, il ne sera disponible que dans la portée du thread. D'autre part, la propriété Current est accessible à tous les threads de l'AppDomain mais lorsqu'elle est appelée, elle renvoie l'instance correcte pour ce thread. Voici le code dont vous avez besoin :

public sealed class Manager
{
    // As you are using the ThreadStatic here you cannot
    // call the static constructor or use the Lazy implimentation for 
    // thread-safty and you have to use the old fashin Lock and anti-pattern.
    private static readonly object _criticalArea = new object();

    [ThreadStatic]
    private static Manager _current;
    public static Manager Current
    {
        get
        {
            if (_current == null)
            {
                lock (_criticalArea)
                {
                    if (_current == null)
                    {
                        _current = new Manager();
                    }
                }
            }
            return _current;
        }
    }

    private Manager()
    {
    }

    public string WhatThreadIsThis { get; set; }
}

[TestClass]
public class SingeltonPerThreadTest
{
    private readonly EventWaitHandle _threadHandler = new EventWaitHandle(false, EventResetMode.AutoReset);
    private string _sharedMemory = "I am the shared memory and yet in main thread :(";

    [TestMethod]
    public void TestSingeltonPerThread()
    {
        // Creates a _current for main thread.
        Manager.Current.WhatThreadIsThis = "I am the main thread :)";

        // Start another thread.
        (new Thread(CallTheThreadBaseSingelton)).Start();

        // Wait for it to be finished.
        _threadHandler.WaitOne();

        Assert.AreEqual("I am the main thread :)", Manager.Current.WhatThreadIsThis, "I am not the main thread :( ");
        Assert.AreEqual("I am the other thread ;)", _sharedMemory, _sharedMemory);
    }

    private void CallTheThreadBaseSingelton()
    {
        // Creates a _current for this thread (this thread is the other one :)) ).
        Manager.Current.WhatThreadIsThis = "I am the other thread ;)";
        _sharedMemory = Manager.Current.WhatThreadIsThis;
        _threadHandler.Set();
    }
}

A la vôtre.

0 votes

Cet article est étiqueté Java, il s'agit d'une réponse C#.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X