我正在寻找一种编写实例工厂的好方法,该工厂只为每个线程创建一个类的实例(换句话说:一个特定于线程的单例)。
让我为清晰起见添加一些示例代码:
首先,定义工厂的界面:
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()
时,类都会在其实例映射中查找当前线程,以查看此线程是否已创建实例。如果没有,它会创建一个新的并记住它。
我看到这个天真的方法有几个问题:
Collections.synchronizedMap
使其成为线程安全的,但我想避免这种情况,因为它意味着无处不在的地方可能会产生大量的性能影响。我正在考虑使用WeakHashMap
代替解决第二个问题,因为一旦密钥不再使用,它允许其条目被垃圾收集,但我也遇到了两个潜在问题:
OpenJDK Source
,每次调用get(...)
或put(...)
时都会启动通过expungeStaleEntries()
释放未使用的密钥,并且可能会涉及多个同步操作,我想避免出于性能原因。是否有一个不同的解决方案,最好不需要使用任何 synchronization
(假设Factory.create()
的所有实现都是线程安全的)?如果它确实需要以某种方式管理并发,我希望它通过java.util.concurrent.atomic
中的类来实现。
答案 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;
}
}