Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/309.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 单例对象工厂:这段代码是线程安全的吗?_Java_Thread Safety_Singleton_Factory_Atomic - Fatal编程技术网

Java 单例对象工厂:这段代码是线程安全的吗?

Java 单例对象工厂:这段代码是线程安全的吗?,java,thread-safety,singleton,factory,atomic,Java,Thread Safety,Singleton,Factory,Atomic,我有一个用于许多单例实现的通用接口。接口定义了可以引发选中异常的初始化方法 我需要一个工厂,它将按需返回缓存的单例实现,并想知道下面的方法是否是线程安全的 更新1:请不要建议任何第三方图书馆,因为这需要获得法律许可,因为可能存在许可问题:-) UPDATE2:此代码可能会在EJB环境中使用,因此最好不要产生额外的线程或使用类似的东西 接口单例 { void init()抛出SingletonException; } 公营单音工厂 { 私有静态ConcurrentMap考虑使用番石榴。例如: pr

我有一个用于许多单例实现的通用接口。接口定义了可以引发选中异常的初始化方法

我需要一个工厂,它将按需返回缓存的单例实现,并想知道下面的方法是否是线程安全的

更新1:请不要建议任何第三方图书馆,因为这需要获得法律许可,因为可能存在许可问题:-)

UPDATE2:此代码可能会在EJB环境中使用,因此最好不要产生额外的线程或使用类似的东西

接口单例
{
void init()抛出SingletonException;
}
公营单音工厂
{
私有静态ConcurrentMap考虑使用番石榴。例如:

private static Cache<Class<? extends Singleton>, Singleton> singletons = CacheBuilder.newBuilder()
   .build(
       new CacheLoader<Class<? extends Singleton>, Singleton>() {
         public Singleton load(Class<? extends Singleton> key) throws SingletonException {
           try {
             Singleton singleton = key.newInstance();
             singleton.init();
             return singleton;
           }
           catch (SingletonException se) {
             throw se;
           }
           catch (Exception e) {
             throw new SingletonException(e);
           }
         }
       });

public static <T extends Singleton> T getSingletonInstance(Class<T> clazz) {
  return (T)singletons.get(clazz);
}

private static Cache由于
Cache.containsKey(key)
检查和
Cache.putIfAbsent(key,ref)
调用之间存在间隙,因此代码通常不是线程安全的。两个线程可以同时调用方法(特别是在多核/处理器系统上)并都执行
containsKey()
检查,然后都尝试执行put和creation操作


我将使用锁或在某种监视器上进行同步来保护
getSingleTonistNACE()方法的执行。

拥有所有这些并发/原子的东西将导致更多的锁问题,而不仅仅是将

synchronized(clazz){}
getter周围的块。原子引用用于更新的引用,您不希望发生冲突。在这里,您只有一个编写器,所以您不关心这一点

您可以通过使用hashmap对其进行进一步优化,并且只有在未命中时,才使用同步块:

public static <T> T get(Class<T> cls){
    // No lock try
    T ref = cache.get(cls);
    if(ref != null){
        return ref;
    }
    // Miss, so use create lock
    synchronized(cls){ // singletons are double created
        synchronized(cache){ // Prevent table rebuild/transfer contentions -- RARE
            // Double check create if lock backed up
            ref = cache.get(cls);
            if(ref == null){
                ref = cls.newInstance();
                cache.put(cls,ref);
            }
            return ref;
        }
    }
}
publicstatict-get(类cls){
//无锁尝试
T ref=cache.get(cls);
如果(ref!=null){
返回ref;
}
//小姐,请使用创建锁
已同步(cls){//单例是双重创建的
已同步(缓存){//防止表重建/传输冲突--很少
//再次检查是否备份了创建锁
ref=cache.get(cls);
如果(ref==null){
ref=cls.newInstance();
cache.put(cls,ref);
}
返回ref;
}
}
}

> > P>这看起来是可行的,虽然我可能考虑某种睡眠,即使是纳秒,也可以是在测试时要设置的基准。自旋测试循环会非常昂贵。

也可以考虑通过将<代码> AtomicReference < /代码>改为<代码> ReaDeDeUnEuffor()/代码>,从而可以避免<代码>包含密钥()/代码>,然后>代码> PuthabBebug()/<代码>竞争条件。因此代码将是:

AtomicReference<T> ref = (AtomicReference<T>) CACHE.get(key);
if (ref != null) {
    return readEventually(ref);
}

AtomicReference<T> newRef = new AtomicReference<T>(null);
AtomicReference<T> oldRef = CACHE.putIfAbsent(key, newRef);
if (oldRef != null) {
    return readEventually(oldRef);
}
...
AtomicReference=(AtomicReference)CACHE.get(key);
如果(ref!=null){
返回ref;
}
AtomicReference newRef=新的AtomicReference(null);
AtomicReference oldRef=CACHE.putIfAbsent(key,newRef);
如果(oldRef!=null){
返回READREF(oldRef);
}
...

为了避免同步,您已经做了很多工作,我认为这样做的原因是出于性能考虑。您是否测试过,与同步解决方案相比,这是否确实提高了性能

我询问的原因是并发类往往比非并发类慢,更不用说原子引用的额外重定向级别了。根据线程争用情况,简单的同步解决方案实际上可能更快(并且更容易验证正确性)


此外,我认为在调用instance.init()期间引发SingletonException时,可能会导致无限循环。原因是等待ReadFinally的并发线程永远不会找到其实例(因为在另一个线程初始化实例时引发了异常)。可能这是您案例的正确行为,或者您希望为实例设置一些特殊值,以触发异常,并将其抛出到等待的线程。

google“Memoizer”.基本上,使用
Future

代替
AtomicReference
,他通过将
AtomicReference
放入一个
null
值来防止这种情况。如果
putIfAbsent()
不返回null,它只是将其丢弃并调用get。这就是循环的要点。谢谢!我考虑过这种方法,但在“Java并发实践”中有一个推理书中提到,中等负载下基于原子的算法优于基于锁类的算法,而基于锁类的算法又优于基于内在锁的算法。然而,我倾向于你建议的代码,因为它更具可读性:-)是的,但作为一个单例,你永远不会达到负载,因为创建是唯一锁定的部分。获取程序都是不同步的根据我的经验,旋转总是比锁阻塞更糟糕。除了我之前的评论之外,我认为普通的java.util.HashMap在这里是不够的,因为
cache.put(cls,ref)
可以触发内部表的重建,从而
cache.get(cls)
可以看到处于非一致状态的映射,因为后一个调用是由
synchronized
块生成的。好吧,我刚刚检查了源代码,您是正确的,如果在对两个GET进行访问的同时,一行两次调整和传输实体表的大小,可能会有一个非常遥远的机会。这是非常可笑的rem注意,为了防止双放入重建问题,可以添加同步(缓存)关于doublecheck/put块——我正在编辑我的答案来说明这一点。小心,即使有额外的同步,除非您在所有GET上同步,否则您仍然有可能与常规HashMap出现不一致的状态。此外,我相信即使您已经同步了写入,因为