对一种基于CAS的Singleton实现方式的探讨
来源:互联网 发布:安卓斗地主源码 编辑:程序博客网 时间:2024/05/21 19:51
看到这样一种单例的实现方式:
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();}
其中用到了CAS,CAS以原子方式更新内存中相应的值,从而保证了多线程环境下共享变量更新操作的同步。的确,这种方式可以保证每次调用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/
- 对一种基于CAS的Singleton实现方式的探讨
- [OC]Singleton的一种简便实现方式
- 乐观锁的一种实现方式——CAS
- 乐观锁的一种实现方式——CAS
- 乐观锁的一种实现方式——CAS
- c# singleton 的一种实现.
- Singleton的实现方式
- 一种基于RBAC的扩展模型探讨
- 一种exe启动方式的设计探讨
- 好的Singleton实现方式
- 一种基于CAS的无锁并发HashTable设计及C代码实现
- J.U.C--乐观锁与悲观锁以及乐观锁的一种实现方式-CAS
- Java并发问题--乐观锁与悲观锁以及乐观锁的一种实现方式-CAS
- 乐观锁与悲观锁以及乐观锁的一种实现方式-CAS
- 一种基于智能卡登录Windows系统的实现方式
- 一种基于CAS生成唯一ID的方法
- CAS SSO对手机应用支持的一种思路
- 关于多态实现Singleton模式的探讨
- windows下安装,配置gcc编译器
- MySQL:Table XXX is marked as crashed and should be repaired
- 深入理解JavaScript系列(22):S.O.L.I.D五大原则之依赖倒置原则DIP
- div+css实现九宫格效果
- Android Jni开发环境搭建
- 对一种基于CAS的Singleton实现方式的探讨
- 分享的力量
- 模拟 POJ-1581 A Contesting Decision
- flv 格式分析3
- js变量类型及其声明方式
- NLTK的词性
- 面对生产主管 ERP实施顾问如何“亮剑” (转载)
- 用eclipse创建java web工程
- 国外100个优秀的儿童网站(100 Top Kid Sites)