前言

好久没写博客,强迫自己写一篇。只是总结一下自己学习的单例模式。

说明

单例模式的定义,摘自baike:

单例模式最初的定义出现于《设计模式》(艾迪生维斯理, 1994):“保证一个类仅有一个实例,并提供一个访问它的全局访问点。”

Java中单例模式定义:“一个类有且仅有一个实例,并且自行实例化向整个系统提供。”

懒加载(懒汉):

只有主动调用实例化方法,才实例化单例的类。即,主动调用类似getInstance()返回实例对象。

非懒加载(饿汉):

主动调用实例化方法、或单例类被被动加载时,单例类就被实例化。

(被动加载,假设情况A、B都是单例,且B继承A。现在主动实例化B,但由于类加载关系,会先实例化父类A。此时A类就是被动的被类加载器加载。)

懒汉或饿汉的抉择:

假设有一个[查询功能]单例类的实例化很占用内存空间[10M]、很消耗时间[10s]。

如果是app饿汉,即启动app就实例该单例,那么会让启动app变慢[10s]、且多占用内存[10M]。这[10M]一直被浪费,直到用户使用到[查询功能]。所以建议用懒汉。

如果是web饿汉,虽然会让web启动变慢[10s]、且[10M]内存长时间被浪费。但占用的是服务器资源,对用户来说,第一次使用[查询功能]是良好体验。因为不用花费[10s]去实例化。

否则用户在第一次使用[查询功能]时,还要多等到[10s]去实例化单例。所以建议用饿汉。

(只是简单举例说明,在spring、hibernate中有大量的懒加载机制。但理解不深)

单例模式中的线程安全:

指的是在任何情况下,不会因为多线程的关系造成单例类被多次实例化。而不是指单例类中的成员变量、或方法是线程安全的。

关于网上介绍的单例模式的5种、或7种单例写法:

在我看的几篇博客,其实都只能算5种。7种中有2对只是java上写法的不同,实质是一样的。

建议单例写法:枚举单例模式 (但android可能要注意)。

1、“使用枚举除了线程安全和防止反射强行调用构造器之外,还提供了自动序列化机制,防止反序列化的时候创建新的对象。因此,Effective Java推荐尽可能地使用枚举来实现单例。”

2、android中枚举使用问题: “Enums often require more than twice as much memory as static constants. You should strictly avoid using enums on Android.”

枚举写法是不是懒加载:

个人理解:枚举写法是懒加载。

根据字节码可以看出,enum类是final类(不能被其他类继承)。所以不会存在被子类被动加载。

enum类不会被反射、序列化构造对象。所以只有在主动调用时,enum类才被类加载器加载。

GC会不会回收长时间不使用的单例对象:

个人观点:不会。

除开枚举写法,所有单例类实例化后都是赋值给一个static的引用。e.g. private static Singleton instance = new Singleton();

所以,在内存中一直存在一个引用instance指向单例的实例化对象new Singleton()。导致GC不会回收此单例类的实例化对象。

然后对于枚举写法,根据枚举的特性,在被加载后是不会被GC回收的。(对枚举理解不深,所以不一定对。)

详细可见: 单例模式讨论篇:单例模式与垃圾回收

一、懒汉基本的单例模式(不建议这么写)
 /** 懒加载,线程不安全*/
public class LazyInsec {
private static LazyInsec instance; private LazyInsec(){} public static LazyInsec getInstance(){
if(instance == null) instance = new LazyInsec();
return instance;
}
}

除非是单线程,不然不建议这么写。

(懒汉:可以看出instance只有在主动调用getInstance()时才实例化。)

 /** 懒加载,线程安全。*/
public class LazySec {
private static LazySec instance;
private LazySec(){}
/**
* 区别只在sync
* @see LazySec#getInstance()
*/
public static synchronized LazySec getInstance(){
if(instance == null) instance = new LazySec();
return instance;
} /**
* 线程知识。作用一样。
* @see #getInstance()
*/
public static LazySec getInstance2(){
synchronized (LazySec.class){
if(instance == null) instance = new LazySec();
return instance;
}
}
}

不建议使用的原因是:同步影响并发性。且在绝大多数情况下是不需要这同步检测的。

二、饿汉基本的单例模式
 /**
* 单例模式03:积极加载(饿汉)、线程安全。<br/>
*/
public class ActiveSec {
/**
* 饿汉:在类装载时就实例化。避免了多线程同步问题,所以线程安全。<br/>
* 讨论:不能说积极加载比懒加载就差。积极加载只是在非必要的时候,就实例化了。
*/
private static ActiveSec instance = new ActiveSec();
private ActiveSec(){} public static ActiveSec getInstance(){
return instance;
}
} /**
* 单例模式04:饿汉变种、线程安全。<br/>
* 网上也说了和{@link ActiveSec}差不多。但根据我对JVM的理解,其实只是写法不一样的区别。
*/
class ActiveSec2{
private static ActiveSec2 instance;
static{
instance = new ActiveSec2();
}
private ActiveSec2(){} public static ActiveSec2 getInstance(){
return instance;
}
}

饿汉:可以看出只要类被ClassLoader加载,那么就马上实例化。(static修饰符特性)

其线程安全是通过类加载器ClassLoader的特性:同一个类,只能被相同的ClassLoader加载一次。

三、静态内部类 - 懒汉
 /**
* 单例模式05:懒加载、线程安全。<br/>
* 区别03/04:前者是只要class类被装载,那么instance就赋值实例化对象(饿汉)。
* <br/>而此运用静态内部类特性,只有显示调过<code>getInstance()<code/>才会装载InnerClass,才会new LazySecInner();
* <br/>懒加载的目的:1.实例化相当消耗资源,让其在真正使用时才实例化。2.class可能被动的被装载,此时实例化是没意义的。
* <br/>
*/
public class LazySecInner {
private static class InnerClass{
private static final LazySecInner INSTANCE = new LazySecInner();
} private LazySecInner(){} public static final LazySecInner getInstance(){
return InnerClass.INSTANCE;
}
}

当LazySecInner被ClassLoader加载时,其内部类InnerClass并没有被ClassLoader加载。

四、双重校验锁(JDK1.5后支持)

 /**
* 单例模式06:双重校验锁。
*/
public class DualSync {
private volatile static DualSync instance; private DualSync(){} public static DualSync getInstance(){
if(instance == null){
synchronized (DualSync.class){
if(instance == null){
instance = new DualSync();
}
}
}
return instance;
}
}

写法太复杂,而且效率低下。虽然可能在jdk1.5+对多线程做了很多优化,但具体没了解。所以不怎么考虑这种写法。

****特别 为什么不建议使用以上写法。

1、可以通过反射得到新的实例化对象。

public class Singleton {
private static Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
if(instance == null) instance = new Singleton();
return instance;
}
} class test{
public static void main(String[] args) throws Exception {
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance(); Class clazz = Class.forName(Singleton.class.getName());
Constructor[] constructors = clazz.getDeclaredConstructors();
constructors[0].setAccessible(true);
Singleton s3 = (Singleton) constructors[0].newInstance(); System.out.println(s1 == s2); // true
System.out.println(s1 == s3); // false
}
}

解决办法:

2、序列化和反序列化

这里测试是用的fastjson序列化。可以看出s1==s2、s1!=s3。所以通过反序列化能破坏单例。

解决办法:

以上是针对fastjson的自定义序列化解析,如果是Serializable解决方案不一样。

总之,要想办法自定义解析序列化。达到返回单例的目的。

3、多个ClassLoader破坏单例

摘自网上的解决方案。(暂时没用过多个ClassLoader,所以暂时不详细测试)

private static Class getClass(String classname) throws ClassNotFoundException {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
if(classLoader == null) classLoader = Singleton.class.getClassLoader();
return (classLoader.loadClass(classname));
}
}

JVM在搜索类的时候,又是如何判定两个class是相同的呢?

JVM在判定两个class是否相同时,不仅要判断两个类名是否相同,而且要判断是否由同一个类加载器实例加载的。只有两者同时满足的情况下,JVM才认为这两个class是相同的。

就算两个class是同一份class字节码,如果被两个不同的ClassLoader实例所加载,JVM也会认为它们是两个不同class。

详细浏览: 深入分析Java ClassLoader原理

五、枚举单例模式(最推荐的写法)
/**
* 单例模式06:利用枚举。<br/>
* "这种方式是Effective Java作者Josh Bloch提倡的方式,避免多线程同步的同时,还能防止反序列化重新创建新的对象."
*/
public enum LazySecEnum {
INSTANCE;
public void anyMethod(){
//some code...
}
}

1、枚举不能通过反射实例化,所以也不会存在被反射破坏。

2、枚举不会被反序列化破坏。(枚举知识,不是很了解其原因) 参考:  深度分析 Java 的枚举类型:枚举的线程安全性及序列化问题

3、(仅作参考)枚举会被多个ClassLoader破坏。(不太确定,没有测试过。且对JVM、ClassLoader理解也不深。所以这只做参考理解)

对于android中枚举:

Enums often require more than twice as much memory as static constants. You should strictly avoid using enums on Android

附录:

你真的会写单例模式吗——Java实现 (重点最后的说明)

Java:单例模式的七种写法

深入Java单例模式

单例模式讨论篇:单例模式与垃圾回收

深度分析 Java 的枚举类型:枚举的线程安全性及序列化问题

深入分析Java ClassLoader原理

【pattern】设计模式(1) - 单例模式的更多相关文章

  1. 乐在其中设计模式(C#) - 单例模式(Singleton Pattern)

    原文:乐在其中设计模式(C#) - 单例模式(Singleton Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 单例模式(Singleton Pattern) 作者:weba ...

  2. Java设计模式03:常用设计模式之单例模式(创建型模式)

    1.  Java之单例模式(Singleton Pattern ) 单例模式是一种常见的设计模式,单例模式分三种:懒汉式单例.饿汉式单例.登记式单例三种. 单例模式有一下特点: 1.单例类只能有一个实 ...

  3. iOS设计模式(02):单例模式

    iOS设计模式(02):单例模式 singleton-design-pattern 什么是单例模式? 单例模式是一个类在系统中只有一个实例对象.通过全局的一个入口点对这个实例对象进行访问.在iOS开发 ...

  4. 【设计模式】单例模式-Singleton

    [设计模式]单例模式-SingletonEnsure a class has only one instance, and provide a global point to access of it ...

  5. java设计模式之单例模式你真的会了吗?(懒汉式篇)

    java设计模式之单例模式你真的会了吗?(懒汉式篇) 一.什么是单例模式? 单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一.这种类型的设计模式属于创建型模式,它提供 ...

  6. 设计模式之单例模式(Singleton)

    设计模式之单例模式(Singleton) 设计模式是前辈的一些经验总结之后的精髓,学习设计模式可以针对不同的问题给出更加优雅的解答 单例模式可分为俩种:懒汉模式和饿汉模式.俩种模式分别有不同的优势和缺 ...

  7. GJM : C#设计模式(1)——单例模式

    感谢您的阅读.喜欢的.有用的就请大哥大嫂们高抬贵手"推荐一下"吧!你的精神支持是博主强大的写作动力以及转载收藏动力.欢迎转载! 版权声明:本文原创发表于 [请点击连接前往] ,未经 ...

  8. java设计模式之单例模式(几种写法及比较)

    概念: Java中单例模式是一种常见的设计模式,单例模式的写法有好几种,这里主要介绍三种:懒汉式单例.饿汉式单例.登记式单例. 单例模式有以下特点: 1.单例类只能有一个实例. 2.单例类必须自己创建 ...

  9. 每天一个设计模式-4 单例模式(Singleton)

    每天一个设计模式-4 单例模式(Singleton) 1.实际生活的例子 有一天,你的自行车的某个螺丝钉松了,修车铺离你家比较远,而附近的五金店有卖扳手:因此,你决定去五金店买一个扳手,自己把螺丝钉固 ...

  10. 设计模式之单例模式的简单demo

    /* * 设计模式之单例模式的简单demo */ class Single { /* * 创建一个本类对象. * 和get/set方法思想一样,类不能直接调用对象 * 所以用private限制权限 * ...

随机推荐

  1. Codeforces_492_E

    http://codeforces.com/problemset/problem/492/E 题目规定了gcd=1,可以在纸上模拟一下,发现每一个起点,都会经历过n个点,n个点都是不同行不同列.可以把 ...

  2. ajax jsonp跨域 【转】

    跨域的基本原理:    JSONP跨域GET请求是一个常用的解决方案,    JSONP的最基本的原理是:动态添加一个<script>标签,而script标签的src属性是没有跨域的限制的 ...

  3. C# 把带有父子关系的数据转化为------树形结构的数据 ,以及 找出父子级关系的数据中里面的根数据Id

    紧接上一篇,将List<Menu>的扁平结构数据, 转换成树形结构的数据 返回给前端   ,   废话不多说,开撸! --------------------- 步骤: 1. 建 Menu ...

  4. Go语言实现:【剑指offer】数据流中的中位数

    该题目来源于牛客网<剑指offer>专题. 如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值.如果从数据流中读出偶数个数值,那么中位 ...

  5. python学习(9)字典的基本应用

    字典是一种通过名字或者关键字引用的得数据结构,其键可以是数字.字符串.元组,这种结构类型也称之为映射.字典类型是Python中唯一內建的映射类型.字典可以理解为列表的升级版. dict是无序的 key ...

  6. shell脚本自动化部署

    由于公司技术部团队较小,没有专门的运维团队,所以运维工作技术部承包了. 一.纯人工部署是这样的: 1. 本地打包:一般 maven clean package 2. 借助xftp上传到服务器对应目录 ...

  7. css3 动画 示例

    /* animation */ .a-bounce,.a-flip,.a-flash,.a-shake,.a-swing,.a-wobble,.a-ring{-webkit-animation:1s ...

  8. qt creator源码全方面分析(2-3-1)

    目录 Using External Tools 使用Qt语言学家 预览QML文件 使用外部文本编辑器 配置外部工具 Using External Tools 您可以直接从Qt Creator中使用外部 ...

  9. 038.Python关于TCP黏包问题

    黏包现象 1 黏包现象演示 服务端 #服务端 import socket sk = socket.socket() # 注册主机到网络 sk.bind( ("127.0.0.1", ...

  10. Mysql 在线新建或重做主从

    1. 前言 以前给 Mysql 数据库做主从,都是在主服务器停服的情况下做的.但是最近有一个项目,已经上线几天了,数据库也单服务器跑了几天,才确定要给 Mysql 服务器做一个主从架构,简单的一主一从 ...