博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android 应该是很全面的单例模式介绍,共七种
阅读量:2122 次
发布时间:2019-04-30

本文共 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 Map
objMap = 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/

你可能感兴趣的文章
如何分析SQL语句
查看>>
结构化查询语言(SQL)原理
查看>>
SQL教程之嵌套SELECT语句
查看>>
几个简单的SQL例子
查看>>
日本語の記号の読み方
查看>>
计算机英语编程中一些单词
查看>>
JavaScript 经典例子
查看>>
判断数据的JS代码
查看>>
js按键事件说明
查看>>
AJAX 初次体验!推荐刚学看这个满好的!
查看>>
AJAX 设计制作 在公司弄的 非得要做出这个养的 真晕!
查看>>
AJAX 自己研究玩的
查看>>
javascript(js)数组操作
查看>>
用JavaScript脚本实现Web页面信息交互
查看>>
window 窗口对象操作
查看>>
公司一位老员工愤然离去的留信!崩溃!
查看>>
C#技巧:网页表单自动填写技术(以gmail为例)
查看>>
C#基础概念二十五问
查看>>
C#在Excel中将连续多列相同数据项合并
查看>>
C#如何把html中的相对路径变成绝对路径
查看>>