特定于线程的单例实例

时间:2014-07-06 18:46:11

标签: java multithreading singleton

我正在寻找一种编写实例工厂的好方法,该工厂只为每个线程创建一个类的实例(换句话说:一个特定于线程的单例)。

让我为清晰起见添加一些示例代码:

首先,定义工厂的界面:

public interface Factory<T> {
    public T create();
}

对于普通单例,我可以创建一个包含另一个工厂的接口实现:

public class SingletonFactory<T> implements Factory<T> {

    private final Factory<T> factory;
    private T instance = null;

    public SingletonFactory(Factory<T> factory) {
        this.factory = factory;
    }

    @Override
    public T create() {
        if (instance==null) instance = factory.create();
        return instance;
    }
}

基本上,第一次调用create()时,此调用将转发到提供的工厂以创建该对象的一个​​实例。此实例已缓存,将来对create()的所有调用都将返回相同的实例。我需要做的就是确保每个对象类型SingletonFactory周围只有T个实例。

假设我想提供一个对象的实例每个线程:我可以这样做:

public class ThreadSingletonFactory<T> implements Factory<T> {

    private final Factory<T>     factory;
    private final Map<Thread, T> instance;

    public ThreadSingletonFactory(Factory<T> factory) {
        this.factory  = factory;
        this.instance = new HashMap<Thread, T>();
    }

    @Override
    public T create() {
        Thread thread = Thread.currentThread();
        T result = instance.get(thread);
        if (result==null) {
            result = factory.create();
            instance.put(thread, result);
        }
        return result;
    }
}

现在,每次调用create()时,类都会在其实例映射中查找当前线程,以查看此线程是否已创建实例。如果没有,它会创建一个新的并记住它。

我看到这个天真的方法有几个问题:

  1. HashMap不是线程安全的,它可能不是问题,因为没有两个线程可以在同一个键上运行,但我不确定。我可以使用Collections.synchronizedMap使其成为线程安全的,但我想避免这种情况,因为它意味着无处不在的地方可能会产生大量的性能影响。
  2. 如果应用程序不使用线程池,但会不断产生大量的短期新线程,那么地图将会增长到可能非常大的范围,并在不再需要的实例中阻塞大量内存。
  3. 我正在考虑使用WeakHashMap代替解决第二个问题,因为一旦密钥不再使用,它​​允许其条目被垃圾收集,但我也遇到了两个潜在问题:

    1. 查看OpenJDK Source,每次调用get(...)put(...)时都会启动通过expungeStaleEntries()释放未使用的密钥,并且可能会涉及多个同步操作,我想避免出于性能原因。
    2. 我仍然不确定我是否也不需要将地图包装到synchronizedMap中以确保我不会遇到并发问题。
    3. 是否有一个不同的解决方案,最好不需要使用任何 synchronization(假设Factory.create()的所有实现都是线程安全的)?如果它确实需要以某种方式管理并发,我希望它通过java.util.concurrent.atomic中的类来实现。

2 个答案:

答案 0 :(得分:4)

根据@JBNizet和@SotiriosDelimanolis的建议,ThreadLocal可能会做到这一点。

我还没有对它进行测试,但这可能就是这样吗?

public class ThreadSingletonFactory<T> implements Factory<T> {

    private final ThreadLocal<T> instance;

    public ThreadSingletonFactory(final Factory<T> factory) {
        this.instance = new ThreadLocal<T>() {
            @Override
            protected T initialValue() {
                return factory.create();
            }
        };
    }

    @Override
    public T create() {
        return instance.get();
    }
}

答案 1 :(得分:0)

I have used the implementation as explained above, I am able to achieve the single object per thread.
Below is my complete implementation
1. Created one singleton class MySingleTon with threadlocal object
2. Created 2 thread class to use the MySingleTon object in the run method
3. Created one class to create the thread object and also the MySingleTon object
4. From the sysout statement, all MySingleTon reference are pointing to the same object

    package example.test;

    public class ThreadLevelSingleton
    {
        public static void main(String[] args)
        {
            Thread33 t3 = new Thread33();
            t3.start();

            Thread44 t4 = new Thread44();
            t4.start();

            MySingleTon m1 = MySingleTon.getInstance();
            MySingleTon m2 = MySingleTon.getInstance();

            System.out.println(Thread.currentThread().getName() + " : " + (m1 == m2));

            MySingleTon tm1 = t3.getMySingleTon();
            MySingleTon tm2 = t4.getMySingleTon();
            System.out.println(Thread.currentThread().getName() + " : " + (tm1.equals(tm2)));
        }
    }

    class MySingleTon
    {
        private MySingleTon()
        {
        }

        private static final ThreadLocal<MySingleTon> t = new ThreadLocal<MySingleTon>()
        {
            @Override
            protected MySingleTon initialValue()
            {
                return new MySingleTon();
            }
        };

        public static MySingleTon getInstance()
        {
            return t.get();
        }
    }

    class Thread33 extends Thread
    {
        MySingleTon m1;

        @Override
        public void run()
        {
            MySingleTon t = MySingleTon.getInstance();
            MySingleTon t1 = MySingleTon.getInstance();
            m1 = MySingleTon.getInstance();
            System.out.println(getName() + " : " + (t == t1));
            System.out.println(t);
            System.out.println(t1);
        }

        MySingleTon getMySingleTon()
        {
            return m1;
        }
    }

    class Thread44 extends Thread
    {
        MySingleTon m1;

        @Override
        public void run()
        {
            MySingleTon t = MySingleTon.getInstance();
            MySingleTon t1 = MySingleTon.getInstance();
            m1 = MySingleTon.getInstance();
            System.out.println(getName() + " : " + (t == t1));
            System.out.println(t);
            System.out.println(t1);
        }

        MySingleTon getMySingleTon()
        {
            return m1;
        }
    }