简体   繁体   中英

Thread-specific singleton instances

I am looking for a good way to write an instance factory that creates exactly one instance of a class for each thread (in other words: a thread-specific singleton).

Let me add some example code for clarity:

First, an interface that defines a factory in general:

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

For normal singletons, I can then create an implementation of this interface that wraps another factory:

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

Basically, the first time that create() is called, this call is forwarded to the provided factory to create one instance of the object. This instance is cached and all future calls to create() will return that same instance. All I need to do is to make sure that there is only one instance of SingletonFactory around per object-type T .

Let's say I want to provide exactly one instance of the object : I could do something like this: 提供对象的一个​​实例:我可以做这样的事情:

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

Now, every time create() is called, the class looks up the current thread in its instance map to see if this thread already has an instance created. If not, it creates a fresh one and remembers it.

I see a couple of issues with this naive approach:

  1. HashMap is not thread safe, it might not be an issue since no two threads will ever operate on the same key, but I'm not sure. I could just use Collections.synchronizedMap to make it thread safe, but I would like to avoid that since it implies tons of synchronizations everywhere which might have a significant performance impact.
  2. If the application doesn't use a thread-pool, but keeps spawning tons and tons of short-lived new threads, the map will grow to potentially enormous size and block a lot of memory with no-longer-needed instances.

I was looking into using a WeakHashMap instead to address the second issue, since it allows its entries to be garbage collected once the keys are no longer in use, but I came across two potential issues with that as well:

  1. Looking at the OpenJDK Source , the freeing of unused keys via expungeStaleEntries() is initiated every time I call get(...) or put(...) and involves potentially multiple synchronized operations, which, again, I would like to avoid for performance reasons.
  2. I am still not sure that I don't also need to wrap the map into a synchronizedMap to make sure I don't run into concurrency issues.

Is there a different solution that preferably does not need to use synchronization (assuming that all implementations of Factory.create() are thread-safe)? synchronization (假设Factory.create()所有实现都是线程安全的)? If it does need to manage concurrency somehow, I would prefer it to do so via the classes in java.util.concurrent.atomic .

As suggested by @JBNizet and @SotiriosDelimanolis, ThreadLocal might do the trick.

I haven't tested it yet, but this might be it then?

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM