Java单例模式几种写法
2015-05-29 12:19
357 查看
/** * * @author Fernando * 饿汉式单例 */ public class Singleton { private static Singleton ins = new Singleton(); private Singleton(){} public static Singleton getInstance(){ return ins; } }
饿汉式提前实例化,没有懒汉式中多线程问题,但不管我们是不是调用getInstance()都会存在一个实例在内存中。
采用内部类式单例类:
public class Singleton { private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } private Singleton (){} public static final Singleton getInstance() { return SingletonHolder.INSTANCE; } }内部类式中,实现了延迟加载,只有我们调用了getInstance(),才会创建唯一的实例到内存中.并且也解决了懒汉式中多线程的问题.解决的方式是利用了Classloader的特性.由于 SingletonHolder 是私有的,除了 getInstance() 之外没有办法访问它,因此它是懒汉式的;
package singleton; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; /** * * @author Fernando 懒汉式单例 */ public class Singleton1 { private static Singleton1 ins; private static int count = -1; private Singleton1() { System.out.println("count:" + (++count)); } public static synchronized Singleton1 getInstance1() {//注意!这里如果不使用同步方法,当两个线程A和B同时进到这个函数, //就会生成两个实例对象,因为此时ins == null。 if (ins == null) ins = new Singleton1(); return ins; } //这样写程序不会出错,因为整个getInstance1是一个整体的"critical section",但就是效率很不好, //因为我们的目的其实只是在第一个初始化instance的时候需要locking(加锁),而后面取用instance的时候,根本不需要线程同步 /** * 使用单例提供的getInstance()方法只能得到同一个单例,除非是使用反射方式,将会得到新的单例 */ public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { Singleton1 ins = Singleton1.getInstance1(); Singleton1 ins1 = Singleton1.getInstance1(); try { Class c = Class.forName(Singleton1.class.getName()); Constructor ct = c.getDeclaredConstructor(); ct.setAccessible(true); Singleton1 ins2 = (Singleton1) ct.newInstance(); } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
双检锁写法:
public class Singleton{ private static Singleton single; //声明静态的单例对象的变量 private Singleton(){} //私有构造方法 public static Singleton getSingle(){ //外部通过此方法可以获取对象 if(single == null){ synchronized (Singleton.class) { //保证了同一时间只能只能有一个对象访问此同步块 if(single == null){ single = new Singleton(); } } } return single; //返回创建好的对象 } }
这段代码看起来很完美,很可惜,它是有问题。主要在于
single = new Singleton()这句,这并非是一个原子操作,事实上在 JVM 中这句话大概做了下面 3 件事情。
给 instance 分配内存
调用 Singleton 的构造函数来初始化成员变量
将instance对象指向分配的内存空间(执行完这步 instance 就为非 null 了)
但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错。
我们只需要将 instance 变量声明成 volatile 就可以了。
public class Singleton { private volatile static Singleton single; //声明成 volatile private Singleton (){} public static Singleton getSingleton() { if (single == null) { synchronized (Singleton.class) { if (single == null) { single = new Singleton(); } } } return instance; } }
有些人认为使用 volatile 的原因是可见性,也就是可以保证线程在本地不会存有 instance 的副本,每次都是去主内存中读取。但其实是不对的。使用 volatile 的主要原因是其另一个特性:禁止指令重排序优化。也就是说,在 volatile 变量的赋值操作后面会有一个内存屏障(生成的汇编代码上),读操作不会被重排序到内存屏障之前。比如上面的例子,取操作必须在执行完 1-2-3 之后或者 1-3-2 之后,不存在执行到 1-3 然后取到值的情况。从「先行发生原则」的角度理解的话,就是对于一个 volatile
变量的写操作都先行发生于后面对这个变量的读操作(这里的“后面”是时间上的先后顺序)。(这段参考自Jark’s blog)
用枚举写单例实在太简单了!这也是它最大的优点。下面这段代码就是声明枚举实例的通常做法。
public enum EasySingleton{ INSTANCE; }
我们可以通过EasySingleton.INSTANCE来访问实例,这比调用getInstance()方法简单多了。创建枚举默认就是线程安全的,所以不需要担心double checked locking,而且还能防止反序列化导致重新创建新的对象。
相关文章推荐
- Java获取时间年、月、日的方法
- hdu 2040 亲和数 (java)
- hdu 2039 三角形 (java)
- Java-->反射的用法(新手都能入门)
- hdu 2041 超级楼梯(java)
- Java中的值传递和引用传递
- Java截取字符串的方法
- Struts2配置详解_配置Action
- Java知识大全
- Struts2配置详解_配置Action之通配符映射
- Java读写Cookie记录的方法
- Java调用浏览器打开网页完整实例
- eclipse luna 4.2 svn 检出 web项目
- Java编码规范
- ANDROID Eclipse Android项目缺少R文件解决方法
- java读取properties文件的方法
- Spring MVC 教程,快速入门,深入分析
- spring错误处理 Build path is incomplete. Cannot find class file for org.springframework.aop.Advisor
- java Future 接口介绍
- 如何创建不可变(Immutable)的Java类或对象