KandQ:单例模式的七种写法及其相关问题解析
设计模式中的单例模式可以有7种写法,这7种写法有各自的优点和缺点:
代码示例(java)及其分析如下:
一、懒汉式
public class Singleton
{
private static Singleton singleton;
private Singleton()
{
}
public static Singleton getInstance()
{
if (singleton == null)
singleton = new Singleton();
return singleton;
}
}
优点:
不是马上就初始化的,当需要使用的时候才进行初始化(即是lazy loading)
缺点:
在并发情况下是线程不安全的
二、懒汉式线程安全版
public class Singleton
{
private static Singleton singleton;
private Singleton()
{
}
public synchronized static Singleton getInstance()
{
if (singleton == null)
singleton = new Singleton();
return singleton;
}
}
优点:
不是类加载之后就进行初始化的,当需要使用的时候才进行初始化(即是lazy loading),且为线程安全的
缺点:
效率低,加了synchronized进行同步之后,效率上有所降低
三、饿汉式
public class Singleton
{
private static Singleton singleton = new Singleton();
private Singleton()
{
}
public static Singleton getInstance()
{
return singleton;
}
}
这种方式基于classloder机制避免了多线程的同步问题,不过,instance在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用getInstance方法,但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance显然没有达到lazy loading的效果。其一个明显的好处就是是线程安全的
四、饿汉式的变种写法
public class Singleton
{
private static Singleton singleton;
static
{
singleton = new Singleton();
}
private Singleton()
{
}
public static Singleton getInstance()
{
return singleton;
}
}
其会在类加载的时候就进行加载。和上面的饿汉式的写法优缺点相同
五、静态内部类方式
public class Singleton
{
private Singleton()
{
}
private static class SingletonHolder
{
private static final Singleton singleton = new Singleton();
}
public static Singleton getInstance()
{
return SingletonHolder.singleton;
}
}
这种方式同样利用了classloder的机制来保证初始化instance时只有一个线程,它跟第三种和第四种方式不同的是(很细微的差别): 第三种和第四种方式是只要Singleton类被装载了,那么instance就会被实例化(没有达到lazy loading效果),而这种方式是Singleton类被装载了,instance不一定被初始化。因为SingletonHolder类没有被主动使用,只有显示通过调用getInstance方法时,才会显示装载SingletonHolder类,从而实例化instance。想象一下,如果实例化instance很消耗资源,我想让他延迟加载,另外一方面,我不希望在Singleton类加载时就实例化,因为我不能确保Singleton类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化instance显然是不合适的。这个时候,这种方式相比第三和第四种方式就显得很合理。
六、采用枚举方式
public enum Singletons
{
INSTANCE;
// 此处表示单例对象里面的各种方法
public void Method()
{
}
}
Effective Java作者Josh Bloch提倡使用枚举的方式去实现单例模式。因为它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,同时写法简单。对于枚举方式创建单例,为何可以避免多线程的同步以及防止反序列化重新创建新的对象这个原因,详见相关博文:K:枚举的线程安全性及其序列化问题
七、双重校验锁
public class Singleton
{
private volatile static Singleton singleton;
private Singleton()
{
}
public static Singleton getInstance()
{
if (singleton == null)
{
synchronized (Singleton.class)
{
if (singleton == null)
{
singleton = new Singleton();
}
}
}
return singleton;
}
}
对于双重校验锁,其是对懒汉式线程安全版的改进,其目的在于减少同步所用的开销。对singleton进行两次判null检查,一次是在同步块外,一次是在同步块内。为什么在同步块内还要再检验一次?因为可能会有多个线程一起进入同步块外的 if,如果在同步块内不进行二次检验的话就会生成多个实例。
对singleton变量使用volatile关键字的原因是,instance = new Singleton()这句,并非是一个原子操作,事实上在 JVM中这句话大概做了下面3件事情:
- 给 instance 分配内存
- 调用 Singleton的构造函数来初始化成员变量
- 将instance对象指向分配的内存空间(执行完这步instance就为非null了)
但是在 JVM的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是1-2-3也可能是1-3-2。如果是后者,则在3执行完毕、2未执行之前,被线程二抢占了,这时instance已经是非null了(但却没有初始化),所以线程二会直接返回instance,然后使用,然后顺理成章地jvm就会报错。
有些人认为使用volatile的原因是可见性,也就是可以保证线程在本地不会存有instance的副本,每次都是去主内存中读取。但其实是不对的。使用volatile的主要原因是其另一个特性:禁止指令重排序优化。也就是说,在volatile变量的赋值操作后面会有一个内存屏障(生成的汇编代码上),读操作不会被重排序到内存屏障之前。比如上面的例子,取操作必须在执行完1-2-3之后或者1-3-2之后,不存在执行到1-3然后取到值的情况。从「先行发生原则」(即happen-before)的角度理解的话,就是对于一个volatile变量的写操作都先行发生于后面对这个变量的读操作(这里的“后面”是时间上的先后顺序)。
对于第一种和第二种写法,实际上其可以归类为懒汉式这一种写法,对于第三种和第四种,其也可以归为饿汉式这一种写法。为此,一般单例都是五种写法。懒汉,饿汉,双重校验锁,枚举和静态内部类
对于单例模式,其有两个问题需要注意:
如果单例由不同的类装载器装入,那便有可能存在多个单例类的实例。假定不是远端存取,例如一些servlet容器对每个servlet使用完全不同的类装载器,这样的话如果有两个servlet访问一个单例类,它们就都会有各自的实例。
如果Singleton实现了java.io.Serializable接口,那么这个类的实例就可能被序列化和复原。不管怎样,如果你序列化一个单例类的对象,接下来复原多个那个对象,那你就会有多个单例类的实例。
对第一个问题修复的办法是:
private static Class getClass(String classname)throws ClassNotFoundException
{
// 获取当前执行线程的上下文类加载器
ClassLoader classLoader = Thread.currentThread()
.getContextClassLoader();
if (classLoader == null)
classLoader = Singleton.class.getClassLoader();
return (classLoader.loadClass(classname));
}
对第二个问题修复的办法是:
class Singletones implements java.io.Serializable
{
private static Singletones INSTANCE = new Singletones();
private Singletones()
{
}
public static Singletones getInstance()
{
return INSTANCE;
}
/*
* 我们反序列化后获得的并不是原来的对象,而是经过重构的新的对象实例。
* ObjectInputStream对象在反序列化的时候,会在从I/O流中读取对象时
* ,调用readResolve()方法。实际上就是用readResolve()中返回的对象直接替换在反序列化过程中重构的对象。
*/
private Object readResolve()
{
return Singletones.getInstance();
}
}
原因详见博文:K:java中序列化的两种方式—Serializable或Externalizable
KandQ:单例模式的七种写法及其相关问题解析的更多相关文章
- Java设计模式之单例模式(七种写法)
Java设计模式之单例模式(七种写法) 第一种,懒汉式,lazy初始化,线程不安全,多线程中无法工作: public class Singleton { private static Singleto ...
- Java 单例模式的七种写法
Java 单例模式的七种写法 第一种(懒汉,线程不安全) public class Singleton { private static Singleton instance; private Sin ...
- Android设计模式之单例模式的七种写法
一 单例模式介绍及它的使用场景 单例模式是应用最广的模式,也是我最先知道的一种设计模式.在深入了解单例模式之前.每当遇到如:getInstance()这样的创建实例的代码时,我都会把它当做一种单例模式 ...
- Java:单例模式的七种写法
第一种(懒汉,线程不安全): 1 public class Singleton { 2 private static Singleton instance; 3 private Singleton ( ...
- Java:单例模式的七种写法(转载)
第一种(懒汉,线程不安全): package Singleton; /** * @echo 2013-10-10 懒汉 线程不安全 */ public class Singleton1 { priva ...
- Java:单例模式的七种写法[转]
第一种(懒汉,线程不安全): 1 public class Singleton { 2 private static Singleton instance; 3 privat ...
- 【JAVA学习】单例模式的七种写法
尊重版权:http://cantellow.iteye.com/blog/838473 第一种(懒汉.线程不安全): Java代码 public class Singleton { private ...
- Java:单例模式的七种写法<转>
第一种(懒汉,线程不安全): 1 public class Singleton { 2 private static Singleton instance; 3 privat ...
- 温故而知新(java实现)单例模式的七种写法
第一种(懒汉,线程不安全): Java代码 public class Singleton { private static Singleton instance; private Singleton ...
随机推荐
- C#使用Xamarin开发可移植移动应用终章(11.获取设备信息与常用组件,开源一个可开发模版.)
前言 系列目录 C#使用Xamarin开发可移植移动应用目录 源码地址:https://github.com/l2999019/DemoApp 可以Star一下,随意 - - 说点什么.. 本系列,终 ...
- 深入讲解HashMap原理
1. HashMap概述: HashMap是基于哈希表的Map接口的非同步实现.此实现提供所有可选的映射操作,并允许使用null值和null键.此类不保证映射的顺序,特别是它不保证该顺序恒久不变 ...
- MYSQL 主从复制---原理
复制的核心步骤 在主库上把数据更改记录到二进制日志(Binary Log)中; 备库将主库上的日志复制到自己的中继日志(Relay Log)中; 备库读取中继日志中的事件,将其重放到备库数据之上; 下 ...
- angularjs 给封装的模态框元素传值,和实现兄弟传值
本例实现封装的元素所放的位置不同,而选择不同的传值,这里举例封装了bootstrap模态框,以后也方便大家去直接使用.方法举例如下:首先主页调用css/js有: <link rel=" ...
- maven项目部署对Oracle jar包的处理
1.正常情况下,我们是访问不到ojdbc.jar的,需要建立一个本地仓. 2.先找到自己的Oracle中ojdbc.jar将其放入到 C:\Users\Administrator 这个目录下,然 ...
- Linux Rsync备份服务介绍及部署守护进程模式
rsync介绍 rsync是一款开源的.快速的.多功能的.可实现全量及增量的本地或远程数据同步备份工具 在常驻模式(daemon mode)下,rsync默认监听TCP端口873,以原生rsync传输 ...
- cygwin下java报错“找不到或无法加载主类”的故障排除
win7 下安装了java,命令行下可以正常运行,cygwin下报错:找不到或无法加载主类. 经排查发现是cygwin的~/.bash_profile中画蛇添足的配置了$CLASSPATH: JAVA ...
- Mysql----关于内联,左联,右联,全联的使用和理解
准备工作:新建两张表 表一:student 填充内容:编号,姓名,班级 表二:school 填充内容:编号,班级,专业 这两张表建好了,意为班级选课表,两张表没有任何主外键的关系,下面进行内联,左联, ...
- 开发H5基本知识摘要
一:开发平台 我在公司开发app主要是在apicloud平台上https://www.apicloud.com/,需要开发的同学可以点击进入这个平台了解: 二:开发工具 既然是在apicloud平台上 ...
- 经验总结22--抓取HTML数据,HtmlAgilityPack(续)
假设获取的数据是HTML的话.我们就须要第三方工具有辅助获取我们须要的数据. 我选用了HtmlAgilityPack这么个工具. 首先肯定去网上下载一个,然后引用到项目中.下载地址:http://ht ...