本文共 4850 字,大约阅读时间需要 16 分钟。
只写实用性
弊端:当有多个线程并行调用 getInstance() 的时候,就会创建多个实例
//######################第一种懒汉式,线程不安全 start#####################public class Singleton { private static Singleton instance; //私有构造,此类不可创建 private Singleton() { } //懒汉,线程不安全 public static Singleton getInstance() { if (instance == null) instance = new Singleton(); return instance; }}//######################第一种懒汉式,线程不安全 end#####################
弊端:做到了线程安全,并且解决了多实例的问题,但是它并不高效
//######################第二种懒汉式,线程安全 start#####################public class Singleton { private static Singleton instance; //私有构造,此类不可创建 private Singleton() { } public static synchronized Singleton getInstance() { if (instance == null) instance = new Singleton(); return instance; }}//######################第二种懒汉式,线程安全 end#####################
弊端:如果该单例类涉及资源较多,创建比较耗时间,无法做到延迟创建对象,类加载时就初始化,浪费内存
//######################第三种饿汉式 start#####################public class Singleton { //类加载时就初始化 private static final Singleton instance = new Singleton(); //私有构造,此类不可创建 private Singleton() { } //懒汉,线程安全 public static Singleton getInstance() { return instance; }}//######################第三种饿汉式 end#####################
volatile的作用:两层含义:
1、可见性,共享性,当一个线程中对这个共享变量修改了,马上会由工作内存写回到主内存,所以其他线程马上就能获取到修改后的值(工作内存是线程独享,主存是线程共享) 2、禁止指令重排序优化(由于编译器的优化,在实际执行的时候可能与我们编写的顺序不同,编译器只保证程序执行结果与源代码相同)详解volatile:
假设线程A执行到 instance=new Singleton();语句,这里看起来是一句代码,但实际上他并不是一个原子操作,这句代码最终会被编译成多条汇编指令,大致做了3件事情: 1)、给Singleton的实例分配内存 2)、调用 Singleton()的构造函数,初始化成员字段 3)、将instance对象指向分配的内存空间(此时 instance 就不是 null 了)但是,由于Java 编译器允许处理器乱序执行,以及JDK1.5之前JMM中Cache、寄存器到主内存回写顺序的规定,上面的第二和第三的顺序是无法保证的。如果是先执行了第三完毕,第二还未被执行之前,被切换到线程B上,这个时候instance因为以及在线程A内执行过第三,instance已经是非空了,所以线程B直接取走instance,在使用的时候会出错。所以用volatile
弊端: volatile在java5以前,即使使用volatile也不能完全避免重复排序,java5才修复的,第一次加载时反应稍慢,也由于Java内存模型的原因偶尔会失败,在高并发环境下也有一定的缺陷
//######################第四种双重校验 start#####################public class Singleton { private static volatile Singleton instance; //私有构造,此类不可创建 private Singleton() { } public static Singleton getInstance() { if (instance == null) synchronized (Singleton.class) { if (instance == null) instance = new Singleton(); } return instance; }}//######################第四种双重校验 end#####################
《Java并发编程实践》一书中推荐使用
由于 Holder 是私有的,除了 getInstance() 之外没有办法访问它,因此它是懒汉式的;同时读取实例的时候不会进行同步,没有性能缺陷
当第一次加载 singleton类时,并不会初始化instance,只有在第一次调用instance方法会导致虚拟机加载Singleton类,这种方式不仅能够确保线程安全,也能够保证单例对象的唯一性,同时也延迟了单例的实例化
//######################第五种静态内部类 start#####################public class Singleton { private static class Holder { private static Singleton instance = new Singleton(); } private Singleton() { } public static Singleton getInstance() { return Holder.instance; }}//######################第五种静态内部类 end#####################
############################# 华丽分割线 #########################################
以上五种单例模式中,在反序列化的情况下它们会出现重新创建对象。
我们知道通过序列化可以将一个单例的实例对象写到磁盘,然后再读取回来,从而有效地获得一个实例。即使构造函数是私有的,反序列化时依然可以通过特殊的途径去创建类的一个新的实例,相当于调用该类的构造函数。反序列化操作提供了一个很特别的钩子函数,类中具有一个私有的readResolve()函数,这个函数可以让开发人员控制对象的反序列化
eg:
public class Singleton implements Serializable { private static final long serialVersionUID = 0L; private static final Singleton instance = new Singleton(); private Singleton() { } public static Singleton getInstance() { return instance; } private Object readResolve() throws ObjectStreamException { return instance; }}
也就是在readResolve方法中将单例对象返回,而不是重新生成一个新的对象。另外需要注意两点:
1、可序列化类中的字段类型不是Java的内置类型,name该字段类型也需要实现Serializable接口 2、如果你调整了可序列化类的内部结构,例如新增、去除某个字段,但没有修改serialversionUID,那么会引发java.io.Invalidclassexception异常导致某个属性为0或者null。此时最好的方案是我们直接将seriaVersionUID设置为0L,这样即使修改了类的内部结构,我们反序列化不会抛出java.io.InvalidclassException,只是那些新修改的字段会为0或null############################# 华丽分割线 #########################################
不用考虑序列化和反射的问题,因为即使反序列化它也不会重新生成新的实例
注意:anndroid官方不推荐使用枚举,每一个枚举值都是一个对象,在使用它时会增加额外的内存消耗
//######################第六种枚举 start#####################public enum Singleton { INSTANCE}//######################第六种枚举 end#####################
############################# 华丽分割线 #########################################
public class SingletonManager { private static MapobjMap = new HashMap<>(); private SingletonManager() { } public static void registerService(String key, Object instance) { if (!objMap.containsKey(key)) { objMap.put(key, instance); } } public static Object getService(String key) { return objMap.get(key); }}
在程序的初始,将多种单例类型注入到一个统一的管理类中,在使用时根据key获取对象对应类型的对象。这种方式使得我们可以管理多种类型的单例,并且在使用时可以通过统一的接口进行取货操作,降低了用户的使用成本,也对用户隐藏了具体实现,降低了耦合度
转载地址:http://vwfrf.baihongyu.com/