对一种基于CAS的Singleton实现方式的探讨



看到这样一种单例的实现方式:

 

public class SingletonByAtomic {
 
    private SingletonByAtomic(){        
}
 
private static AtomicReference<SingletonByAtomic> instance=new AtomicReference<SingletonByAtomic>();
public static SingletonByAtomic getSingletonByAtomic(){
    if(instance.get()==null){
            instance.compareAndSet(null, new SingletonByAtomic());
    }
    return instance.get();
}


 

其中用到了CASCAS以原子方式更新内存中相应的值,从而保证了多线程环境下共享变量更新操作的同步。的确,这种方式可以保证每次调用getSingletonByAtomic方法得到的一定是同一个实例。因此,从功能实现的角度来看,这种做法达到了预期的目的。但是,经过分析和测试,却发现这种方式有一些预期之外的弊病:可能会创建不止一个对象。

 

为了证实这一点,请看测试类:

 

---------------------------------SingleCasTest.java------------------------------------------------

 

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
 
 
/**
 *
 * @author Administrator
 */
public class SingleCasTest {
    static AtomicInteger objectcount=new AtomicInteger();
    
    public static void main(String[] args) throws InterruptedException {
        final CountDownLatch begin=new CountDownLatch(1);
        final CountDownLatch last=new CountDownLatch(1000);
          for(int i=0;i<1000;i++){
              new Thread(new Runnable() {
                  @Override
                  public void run() {
                      try {
                         begin.await();
                         System.out.println(Thread.currentThread().getName()+":begin...");
                          SingletonByAtomic sba=SingletonByAtomic.getSingletonByAtomic();
                         
                          System.out.println(Thread.currentThread().getName()+":OK");
                           last.countDown();
                      } catch (InterruptedException ex) {
                          Logger.getLogger(SingleCasTest.class.getName()).log(Level.SEVERE, null, ex);
                      }
                  }
              }).start();
          }
          begin.countDown();
          last.await();
          System.out.println("new objects: "+objectcount.get());
    }
 
}


--------------------------------------------------------------------------------------------------------

 

在以上代码中,我们创建了1000个线程,并用CountDownLatch控制,让它们都在begin锁上等待,当所有线程创建完毕之后,我们打开锁,让全部线程在同一时刻获得调用getSingletonByAtomic方法的机会。在此类中,我们创建了一个原子方式更新的静态变量objectcount,用于记录生成对象的数量。

下面我们看测试所用的SingletonByAtomic类:

 

-------------------------------------------SingletonByAtomic.java-------------------------------

 

import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;
 
public class SingletonByAtomic {
    private SingletonByAtomic(){
        System.out.println("new object...");
        SingleCasTest.objectcount.getAndIncrement();
        
    }
private static AtomicReference<SingletonByAtomic> instance=new AtomicReference<SingletonByAtomic>();
public static SingletonByAtomic getSingletonByAtomic(){
    if(instance.get()==null){
        
            instance.compareAndSet(null, new SingletonByAtomic());
       
    }
    return instance.get();
}
 
}


------------------------------------------------------------------------------------------------------

 

在构造器中,我们让测试类SingleCasTest中的静态变量 objectcount以原子方式增加1,从而记录了构造器被调用的次数。

 

每个线程完成各自的工作之后,都会将last锁的计数减1,当所有线程执行完毕之后,我们在main方法中打印了新创建对象的数量。

 

本人机器为四核心,每次运行的结果都不是1,从几个到几十个不等。也就是说,我们只希望创建一个对象,而结果却有可能不是一个。

 

接下来让我们将SingletonByAtomic修改成这样:

 

-------------------------------------------SingletonByAtomic.java-------------------------------

 

 

import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;
 
public class SingletonByAtomic {
    private SingletonByAtomic(){
        System.out.println("new object...");
        SingleCasTest.objectcount.getAndIncrement();
        
    }
private static AtomicReference<SingletonByAtomic> instance=new AtomicReference<SingletonByAtomic>();
public static SingletonByAtomic getSingletonByAtomic(){
    if(instance.get()==null){
        try {
            Thread.sleep(100);
            instance.compareAndSet(null, new SingletonByAtomic());
        } catch (InterruptedException ex) {
            Logger.getLogger(SingletonByAtomic.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    return instance.get();
}
 
}
 


--------------------------------------------------------------------------------------------------------

 

在我们完成if(instance.get()==null)条件测试之后,让该线程睡眠100毫秒。经测试,新创建的对象达到了1000个,等于我们创建线程的数量!

 

为什么会产生这样的结果呢?

是的,CAS本身的操作的确是原子方式,但是包装CAS指令的方法并非是全程同步的,当然,在包含CAS指令的方法开始调用之前,参数计算过程中更不是互斥执行的!当一个线程测试instance.get()==null得到true之后,它就一定会调用new SingletonByAtomic(),因为,这并不是CAS方法的一部分,而是它的参数。在调用一个方法之前,需要先将其参数压入栈,当然,需要先计算参数表达式,因此,产生如上结果也就不难预料了。

 

CAS与锁的区别在于,它是非阻塞的,也就是说,它不会去等待一个条件,而是一定会去执行,结果要么成功,要么失败。它的操作时间是可预期的。如果我们的目的是一定要成功执行CAS,那就需要不断循环执行直至成功,同时,建立在成功预期之上大量的准备工作是值得的,但是,如果我们不希望操作一定成功,那为成功操作而做的准备工作就浪费掉了。

 

参考文献

 

http://www.ibm.com/developerworks/cn/java/j-jtp11234/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值