文档章节

JAVA单例之我见

zjg23
 zjg23
发布于 2016/05/08 10:55
字数 1407
阅读 29
收藏 0
点赞 3
评论 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
粉丝 18
博文 120
码字总数 40282
作品 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
为什么我墙裂建议大家使用枚举来实现单例

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

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

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

06/10
0
0
带你了解源码中的 ThreadLocal

这次想来讲讲 ThreadLocal 这个很神奇的东西,最开始接触到这个是看了主席的《开发艺术探索》,后来是在研究 ViewRootImpl 中又碰到一次,而且还发现 Android 中一个小彩蛋,就越发觉得这个东...

请叫我大苏
07/20
0
0
类和对象之单例对象(Singleton)

Scala比Java更为面向对象的特点之一是scala不能定义静态成员,而是代之以定义单例对象。除了用object关键字替换了class关键字以外,单例对象的定义看上去与类定义一致。 当单例对象与某个类共...

柳哥
2014/06/05
0
0
Java语言中单例模式的四种写法

作为设计模式理论中的Helloworld,相信学习java语言的人,都应该听说过单例模式。单例模式作为对象的一种创建模式,它的作用是确保某一个类在整个系统中只有一个实例,而且自行实例化并向整个...

相见欢
2013/02/03
0
20
为什么java中用枚举实现单例模式会更好

枚举单例是java中使用枚举提供一个实例对象来实现单例模式的一种新方法,虽然单例模式在java中早已存在,但枚举单例实际上从java5引入枚举作为它的关键特性之后相对来说还是一个新的概念,这...

zhoujy
2013/06/01
0
0
调用腾讯 AI 接口的 Java 客户端 - Taip

TAIP 是调用腾讯 AI 接口的 Java 客户端,为调用腾讯 AI 功能的开发人员提供了一系列的交互方法。 目前已经接入文字识别、语音识别接口服务调用服务 项目结构介绍 ├── base //基类 ├──...

小帅帅丶
04/24
0
0
面试:用 Java 实现一个 Singleton 模式

面试系列更新后,终于迎来了我们的第一期,我们也将贴近《剑指 Offer》的题目给大家带来 Java 的讲解,个人bogo还是非常推荐《剑指 Offer》作为面试必刷的书籍的,这不,再一次把这本书分享给...

nanchen2251
07/03
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

mysql 主从复制中遇到的错误!

。。。。。

万建宁
13分钟前
0
0
DUBBO 详细介绍

摘要: 主要核心部件: Remoting: 网络通信框架,实现了 sync-over-async 和 request-response 消息机制. RPC: 一个远程过程调用的抽象,支持负载均衡、容灾和集群功能 Registry: 服务目录框架...

明理萝
24分钟前
0
1
4 个快速的 Python 编译器 for 2018

简评:Python 和其他的解释型语言一样经常被吐槽性能不行,所以开发人员为了提升性能创建了不少编译器,本文则选取其中的四个做了基准测试。 Python 其实是一种相当快的语言,但它并不像编译...

极光推送
27分钟前
0
0
spring boot注册多个MQ服务器的问题

关于注册到多个MQ源的文章已经有很多了,这里记录一下声明queue的坑; 如果使用注册bean的方式声明queue,会导致声明的queue同时被注册到所有的MQ源上; //如果使用下面的声明方式,que...

placeholder
28分钟前
0
0
Java面试基础篇——第九篇:BIO,NIO,AIO的区别

现在IO模型主要分三类:BIO(同步阻塞IO),NIO(同步非阻塞IO),AIO()。 先来看看BIO。 1. BIO 服务端接受到请求后,要指派或新建一个线程去处理客户端的IO请求,直到收到断开连接的指令。这么做...

developlee的潇洒人生
33分钟前
0
0
@RequestMapping @ResponseBody 和 @RequestBody 用法与区别

1.@RequestMapping 国际惯例先介绍什么是@RequestMapping,@RequestMapping 是一个用来处理请求地址映射的注解,可用于类或方法上。用于类上,表示类中的所有响应请求的方法都是以该地址作为...

特拉仔
35分钟前
1
0
基于 HTML5 结合互联网+ 的 3D 隧道

前言 目前,物资采购和人力成本是隧道业发展的两大瓶颈。比如依靠民间借贷,融资成本很高;采购价格不透明,没有增值税发票;还有项目管控和供应链管理的问题。成本在不断上升,利润在不断下...

xhload3d
37分钟前
0
0
济南小程序热度分析

原文链接:http://www.jnqianle.cn/company/2072.html

tianma3798
38分钟前
1
0
大数据软件

beats 采集 kafka spark hive es grafana zeppelin

ArlenXu
40分钟前
0
0
Mac item2常用快捷键

标签 新建标签:command + t 关闭标签:command + w 切换标签:command + 数字 command + 左右方向键 切换全屏:command + enter 查找:command + f 分屏 水平分屏:command + d 垂直分屏:c...

说回答
43分钟前
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部