Синглтон-паттерн на поток - PullRequest
       2

Синглтон-паттерн на поток

3 голосов
/ 09 сентября 2011

В своей работе я наткнулся на такой вопрос дизайна:

  • Мне нужен один экземпляр Manager класса на поток
  • Эти экземпляры должны быть доступны глобально, как в шаблоне синглтона через статическую функцию
  • Каждый поток может нуждаться в инициализации своего экземпляра с разными аргументами
  • Время жизни этих экземпляров должно контролироваться, иногда было бы полезно удалить экземпляр и позволить GC собрать его

Первые две точки сделали бы это «синглтоном на поток», если такая вещь существует.

Вот что я придумал (код упрощен, я пропустил проверки безопасности и т. Д.):

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

Как видите, функция clean также может вызываться из закрытых методов. Также обратите внимание, что благодаря использованию статических функций ссылка на экземпляр никогда не просачивается, поэтому экземпляры, назначенные различным потокам, не будут смешиваться.

Это работает вполне нормально, но потом я получил другое требование:

  • Разные потоки могут использовать разные реализации класса Manager

Итак, я определил интерфейс:

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

И модифицировал Manager класс:

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

Пример реализации будет выглядеть так:

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

Класс менеджера работает здесь как фасад, перенаправляя все вызовы соответствующему обработчику. У этого подхода есть одна большая проблема: мне нужно определить все функции как в классе Manager, так и в интерфейсе ManagerHandler. К сожалению, класс Manager не может реализовать интерфейс ManagerHandler, потому что он имеет статические функции, а не методы.

Вопрос в том, можете ли вы придумать лучший / более простой способ достижения всех целей, перечисленных выше, которые были бы свободны от этой проблемы?

Ответы [ 3 ]

1 голос
/ 09 сентября 2011

Если вы собираетесь использовать этот дизайн, необходимо ли Manager полностью скрыть ManagerHandler интерфейс или вы можете выставить его так, чтобы вам не приходилось делегировать каждый метод?

class Manager {
    public static ManagerHandler getHandler() { return local.get(); }
}
1 голос
/ 09 сентября 2011

Вы мало что можете сделать, так как вам нужно прокси-методы интерфейса через статические методы. Я мог думать только о двух способах достижения одной и той же функциональности по-разному:

  1. Если вы используете платформу DI, вы можете избавиться от статического Manager и использовать внедренную реализацию ManagerHandler, которая будет содержать ThreadLocal.
  2. Создание (как в 'генерации байт-кода') статического класса ManagerAccess с использованием методов, найденных в интерфейсе ManagerHandler.

Лично я бы не думал, что статический класс ManagerAccess (который содержит ThreadLocal) является серьезной проблемой проектирования. По крайней мере, до тех пор, пока он выполняет свой собственный набор обязанностей (доступ к экземплярам с областью потока и вызовы прокси) и не рискует никуда больше.

0 голосов
/ 25 января 2018

Хитрость для создания одиночного класса для каждого потока заключается в использовании атрибута ThreadStatic в вашем частном статическом поле _current, что делает его ограниченным потоком.Таким образом, поле _current будет храниться в памяти потоков, которая недоступна для других потоков и не является общей памятью AppDomain.Итак, он будет доступен только в рамках темы.С другой стороны, свойство Current доступно всем потокам в этом AppDomain, но при вызове оно возвращает правильный экземпляр для этого потока.Вот код, который вам нужен:

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

Cheers.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...