文档章节

JAVA单例之我见

zjg23
 zjg23
发布于 2016/05/08 10:55
字数 1407
阅读 29
收藏 0

       单例模式作为设计模式中最简单的一种,是一个被说烂了的东西。但是在项目中还是会发现关于单例模式的一些错误实现,可见单例也并不是我们想象的那么简单。最近陆陆续续看了几篇关于单例的博客,很受启发,所以觉得有必要总结一下(只涉及常用的双重检查锁定、静态内部类、枚举三种单例实现方法),本文将从安全和性能两个方面阐述我对单例模式三种最佳实践的理解。不当之处,请 指正。

1、双重校验锁定

 

//Double Checked locking
public class SingletonByDCL
{
    private Map<Integer, String> configMap;
    private volatile static SingletonByDCL instance = null;

    private SingletonByDCL(Map<Integer, String> configMap)
    {
	this.configMap = configMap;
    }

    public static SingletonByDCL getInstance()
    {
	SingletonByDCL inst = instance;
	if (null == inst)
	{
	    synchronized (SingletonByDCL.class)
	    {
		inst = instance;
		if (null == inst)
		{
		    inst = new SingletonByDCL(ConfigReader.configMap);
		    instance = inst;
		}
	    }
	}
	return inst;
    }
}

 

通过synchronized和volatile实现了线程安全。其中需要注意的是实例变量一定要用volatile修饰。原因可参考Java 单例真的写对了么?。当单例对象需要被序列化时,就应该考虑单例实现的序列化安全,在Singleton中定义readResolve方法,并在该方法中指定要返回的对象的生成策略,就可以方式单例被破坏,原理可参考单例与序列化的那些事儿。可能会有人使用反射强行调用我们的私有构造器,为了保证访问安全,可以修改构造器,让它在创建第二个实例的时候抛异常。

2、静态内部类

 

public class SingletonByInnerStaticClass
{
    private Map<Integer, String> configMap;

    private SingletonByInnerStaticClass(Map<Integer, String> configMap)
    {
	this.configMap = configMap;
    }

    private static class SingletonHolder
    {
	private static SingletonByInnerStaticClass instance = new SingletonByInnerStaticClass(ConfigReader.configMap);
    }

    public static SingletonByInnerStaticClass getInstance()
    {
	return SingletonHolder.instance;
    }
}

 

线程安全,这是 Java 运行环境自动给保证的,在加载的时候,会自动隐形的同步。在访问对象的时候,不需要同步 Java 虚拟机又会自动取消同步。对于序列化安全访问安全的保证,解决方法同“双重检查锁定”。

3、枚举

 

public enum SingletonByEnum
{
    instanse(ConfigReader.configMap);
    
    private Map<Integer,String> configMap;
    
    private SingletonByEnum(Map<Integer,String> configMap)
    {
	this.configMap = configMap;
    }

    public Map<Integer, String> getConfigMap()
    {
        return configMap;
    }
}

当一个Java类第一次被真正使用到的时候静态资源被初始化、Java类的加载和初始化过程都是线程安全的。所以,创建一个enum类型(可用javap查看enum编译后的class文件,从而了解enum包含哪些静态资源)是线程安全的。为了保证枚举类型像Java规范中所说的那样,每一个枚举类型极其定义的枚举变量在JVM中都是唯一的,在枚举类型的序列化和反序列化上,Java做了特殊的规定,从而保证序列化安全

 

private static void testSingletonByEnum() throws IOException, FileNotFoundException, ClassNotFoundException
    {
	// 序列化
	ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempfile"));
	oos.writeObject(SingletonByEnum.instanse);

	// 反序列化
	ObjectInputStream ois = new ObjectInputStream(new FileInputStream("tempfile"));
	SingletonByEnum s = (SingletonByEnum) ois.readObject();

	// 判断是否为同一对象
	if (s == SingletonByEnum.instanse)
	{
	    System.out.println("创建的是同一个实例");
	} else
	{
	    System.out.println("创建的不是同一个实例");
	}
    }

 

创建的是同一个实例

 

 

 

同时java.lang.reflect.Constructor的newInstance()方法中有如下代码,禁止了通过反射构造枚举对象,所以枚举可以保证访问安全。关于枚举如何保证线程安全和序列化安全,可参考深度分析 Java 的枚举类型:枚举的线程安全性及序列化问题

if ((clazz.getModifiers() & Modifier.ENUM) != 0)   
   throw new IllegalArgumentException("Cannot reflectively create enum objects");

4、三种方法的性能比较

三种方法都实现了延迟加载,在8线程同时调用,每个线程调用100000000次的情况下时间对比如下:

SingletonByDCL—》845

SingletonByEnum—》90

SingletonByInnerStaticClass—》89

具体测试代码参见附件工程中的com.zjg.perf.PerformanceTest2

综上所述,Effective Java中推荐使用的枚举实现单例无论从安全还是性能都是有道理的。当然代码没有一劳永逸的写法,只有在特定条件下最合适的写法。在不同的平台、不同的开发环境(尤其是jdk版本)下,也就会有不同的最优解。比如枚举在Android平台上却是不被推荐的。在这篇Android Training中明确指出:

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

再比如双重检查锁法,不能在jdk1.5之前使用,而在Android平台上使用就比较放心了(一般Android都是jdk1.6以上了,不仅修正了volatile的语义问题,还加入了不少锁优化,使得多线程同步的开销降低不少)。

参考文章:

http://blog.jobbole.com/94074/  深度分析 Java 的枚举类型:枚举的线程安全性及序列化问题

http://www.importnew.com/18872.html  你真的会写单例模式吗——Java实现

http://www.hollischuang.com/archives/1144  单例与序列化的那些事儿

http://www.race604.com/java-double-checked-singleton/?utm_source=tuicool&utm_medium=referral Java 单例真的写对了么?

demo工程:https://git.oschina.net/zjg23/SingletonDemo

© 著作权归作者所有

共有 人打赏支持
zjg23
粉丝 19
博文 122
码字总数 40982
作品 0
济南
程序员
第三章 spring-bean之DefaultSingletonBeanRegistry(3)

前言 SingletonBeanRegistry是一个非常重要的接口,用于注册,获得,管理singleton对象。 SingletonBeanRegistry目前唯一的实现是DefaultSingletonBeanRegistry,DefaultSingletonBeanRegis...

鸟菜啊
07/18
0
0
Java ThreadLocal的用法解析

简介 java中经常使用ThreadLocal作为处理高并发访问的可选手段,ThreadLocal并不是一个线程,而是”以线程为作用域“的一种面向对象的数据结构。其用法的也有着让人难以理解的怪异性。 代码实...

IamOkay
2014/10/25
0
0
去投资银行面试会遇到的10个Java问题

本文由ImportNew -大瓜细瓜 翻译自dzone。欢迎加入翻译小组。转载请见文末要求。 很多Java开发人员会到巴克莱、瑞士信贷、花旗等投资银行申请Java开发职位,但他们中很多人都不知道面试时会遇...

ImportNew
07/25
0
0
为什么我墙裂建议大家使用枚举来实现单例。

关于单例模式,我的博客中有很多文章介绍过。作为23种设计模式中最为常用的设计模式,单例模式并没有想象的那么简单。因为在设计单例的时候要考虑很多问题,比如线程安全问题、序列化对单例的...

06/10
0
0
为什么我墙裂建议大家使用枚举来实现单例

我们知道,单例模式,一般有七种写法,那么这七种写法中,最好的是哪一种呢?为什么呢?本文就来抽丝剥茧一下。 哪种写单例的方式最好 在StakcOverflow中,有一个关于What is an efficient ...

冷_6986
06/13
0
0

没有更多内容

加载失败,请刷新页面

加载更多

OSChina 周五乱弹 —— 想不想把92年的萝莉退货

Osc乱弹歌单(2018)请戳(这里) 【今日歌曲】 @罗马的王:分享松澤由美的单曲《地球ぎ》 很久没看圣斗士星矢了 《地球ぎ》- 松澤由美 手机党少年们想听歌,请使劲儿戳(这里) @开源中国首...

小小编辑
11分钟前
3
1
springBoot条件配置

本篇介绍下,如何通过springboot的条件配置,控制Bean的创建 介绍下开发环境 JDK版本1.8 springboot版本是1.5.2 开发工具为 intellij idea(2018.2) 开发环境为 15款MacBook Pro 前言 很多时候,...

贺小五
30分钟前
0
0
javascript source map 的使用

之前发现VS.NET会为压缩的js文添加一个与文件名同名的.map文件,一直没有搞懂他是用来做什么的,直接删除掉运行时浏览器又会报错,后来google了一直才真正搞懂了这个小小的map文件背后的巨大...

粒子数反转
昨天
1
0
谈谈如何学Linux和它在如今社会的影响

昨天,还在农耕脑力社会,今天已经人工智能技术、大数据、信息技术的科技社会了,高速开展并迅速浸透到当今科技社会的各个方面,Linux日益成为人们信息时代的到来,更加考验我们对信息的处理程...

linux-tao
昨天
0
0
学习设计模式——中介者模式

1. 认识中介者模式 1. 定义:用一个中介对象来封装一系列的对象交互行为,中介者使得各对象不需要显式的互相引用,从而使其松散耦合,独立的改变他们之间的交互。 2. 结构: Mediator:中介者...

江左煤郎
昨天
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部