簡體   English   中英

每線程單例模式

[英]Per thread singleton pattern

在我的工作中,我偶然發現了這樣一個設計問題:

  • 我需要每個線程一個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類在此處作為外觀,將所有調用轉發到適當的處理程序。 這種方法存在一個大問題:我需要在Manager類和ManagerHandler接口中定義所有函數。 不幸的是, Manager類無法實現ManagerHandler接口,因為它具有靜態函數而不是方法。

問題是:你能想到一個更好/更簡單的方法來實現我上面列出的所有目標,而這個目標是沒有這個問題的嗎?

您無能為力,因為您基本上需要通過靜態方法代理接口方法。 我只能想到兩種不同方式實現相同功能的方法:

  1. 如果你正在使用DI框架,你可以擺脫靜態Manager並使用一個注入的ManagerHandler實現,它將包含ThreadLocal
  2. 使用ManagerHandler接口中的方法生成(如'字節碼生成')靜態ManagerAccess類。

就個人而言,我不會想到將靜態ManagerAccess類(包含ThreadLocal )作為一個嚴重的設計問題。 至少只要它保持自己的職責集(訪問線程范圍的實例和代理調用),並且不冒險在其他任何地方。

如果您正在使用此設計, Manager是否有必要完全隱藏ManagerHandler接口,或者您是否需要公開它以便您不必委托每個方法?

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

為每個線程類創建單例的技巧是在私有靜態_current字段上使用ThreadStatic屬性,這使得它由線程限定。 這樣,_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();
    }
}

干杯。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM