Java Singleton(单例模式) 实现详解
什么是单例模式?
Intend:Ensure a class only has one instance, and provide a global point of access to it.
目标:保证一个类只有一个实例,并提供全局访问点
--------(《设计模式:可复用面向对象软件的基础》
就运行机制来说,就是一个类,在运行过程中只存在一份内存空间,外部的对象想使用它,都只会调用那部分内存。
其目的有实现唯一控制访问,节约资源,共享单例实例数据。
单例基础实现有两种思路,
1.Eager initialization:在加载类时构造;2.Lazy Initialization:在类使用时构造;3
1.Eager initialization适用于高频率调用,其由于预先创建好Singleton实例会在初始化时使用跟多时间,但在获得实例时无额外开销
其典型代码如下:
- public class EagerInitSingleton {
- //构建实例
- private static final EagerInitSingleton SINGLE_INSTANCE = new EagerInitSingleton();
- //私有化构造器
- private EagerInitSingleton(){}
- //获得实例
- public static EagerInitSingleton getInstance(){
- return SINGLE_INSTANCE;
- }
- }
换一种思路,由于类内静态块也只在类加载时运行一次,所以也可用它来代替构造单例:
- public class EagerInitSingleton {
- //构建实例
- //private static final EagerInitSingleton instance = new EagerInitSingleton();
- //此处不构造
- private static StaticBlockSingleton instance;
- //使用静态块构造
- static{
- try{
- instance = new StaticBlockSingleton();
- }catch(Exception e){
- throw new RuntimeException("Exception occured in creating singleton instance");
- }
- }
- //私有化构造器
- private EagerInitSingleton(){}
- //获得实例
- public static EagerInitSingleton getInstance(){
- return instance;
- }
- }
2.Lazy Initialization适用于低频率调用,由于只有使用时才构建Singleton实例,在调用时会有系列判断过程所以会有额外开销
2.1Lazy Initialization单线程版
其初步实现如下:
- public class LazyInitSingleton {
- private static LazyInitSingleton SINGLE_INSTANCE = null;
- //私有化构造器
- private LazyInitSingleton() {}
- //构造实例
- public static LazyInitSingleton getInstance() {
- if (SINGLE_INSTANCE == null) {
- SINGLE_INSTANCE = new LazyInitSingleton();//判断未构造后再构造
- }
- return SINGLE_INSTANCE;
- }
- }
2.2Lazy Initialization多线程版
在多线程下,可能多个线程在较短时间内一同调用 getInstance()方法,且判断
- SINGLE_INSTANCE == null
结果都为true,则2.1Lazy Initialization单线程版 会构造多个实例,即单例模式失效
作为修正
2.2.1synchronized关键字第一版
可考虑使用synchronized关键字同步获取方法
- public class LazyInitSingleton {
- private static LazyInitSingleton SINGLE_INSTANCE = null;
- //私有化构造器
- private LazyInitSingleton() {}
- //构造实例,加入synchronized关键字
- public static synchronized LazyInitSingleton getInstance() {
- if (SINGLE_INSTANCE == null) {
- SINGLE_INSTANCE = new LazyInitSingleton();//判断未构造后再构造
- }
- return SINGLE_INSTANCE;
- }
- }
2.2.2synchronized关键字第二版(double checked locking 二次判断锁)
以上可实现线程安全,但由于使用了synchronized关键字实现锁定控制,getInstance()方法性能下降,造成瓶颈。分析到需求构建操作只限于未构建判断后第一次调用getInstance()方法,即构建为低频操作,所以完全可以在判断已经构建后直接返回,而不需要使用锁,仅在判断需要构建后才进行锁定:
- public class LazyInitSingleton {
- private static LazyInitSingleton SINGLE_INSTANCE = null;
- //私有化构造器
- private LazyInitSingleton() {}
- //构造实例
- public static synchronized LazyInitSingleton getInstance() {
- if (SINGLE_INSTANCE == null) {
- synchronized(LazyInitSingleton.class){
- if (SINGLE_INSTANCE == null) {
- SINGLE_INSTANCE = new LazyInitSingleton();//判断未构造后再构造
- }
- }
- }
- return SINGLE_INSTANCE;
- }
- }
3利用静态内部类实现懒加载
JVM仅在使用时加载静态资源,当类加载时,静态内部类不会加载,仅当静态内部类在使用时会被加载,且实现顺序初始化即加载是线程安全的,利用这一性质,我们可以实现懒加载
- public class NestedSingleton {
- private NestedSingleton() {}
- //静态内部类,只初始化一次
- private static class SingletonClassHolder {
- static final NestedSingleton SINGLE_INSTANCE = new NestedSingleton();
- }
- //调用静态内部类方法得到单例
- public static NestedSingleton getInstance() {
- return SingletonClassHolder.SINGLE_INSTANCE;
- }
- }
4.使用Enum
由于枚举类是线程安全的,可以直接使用枚举创建单例。但考虑到枚举无法继承,所以只在特定情况下使用
- public enum EnumSingleton {
- INSTANCE;
- }
附1 利用反射机制破解单例(非Enum形式的单例,)
单例形式的类可被反射破解,从而使用单例失效,即将其Private构造器,通过反射形式暴露出来,并进行实例的构造
- public static void main(String[] args) {
- NestedSingleton nestedSingleton = NestedSingleton.getInstance();
- NestedSingleton nestedSingleton2 = null;
- try {
- //暴露构造器
- Constructor[] constructors = nestedSingleton.getClass().getDeclaredConstructors();
- Constructor constructor = constructors[1];
- constructor.setAccessible(true);
- nestedSingleton2 = (NestedSingleton)constructor.newInstance();
- } catch (Exception e ) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- System.out.println(nestedSingleton.hashCode());
- System.out.println(nestedSingleton2.hashCode());
- }
- }
为防止以上情况,可在构造器中抛出异常,以阻止新的实例产生
- public class NestedSingleton {
- private NestedSingleton() {
- synchronized (NestedSingleton.class) {
- //判断是否已有实例
- if(SingletonClassHolder.SINGLE_INSTANCE != null){
- throw new RuntimeException("new another instance!");
- }
- }
- }
- //静态内部类,只初始化一次
- private static class SingletonClassHolder {
- static final NestedSingleton SINGLE_INSTANCE = new NestedSingleton();
- }
- //调用静态内部类方法得到单例
- public static NestedSingleton getInstance() {
- return SingletonClassHolder.SINGLE_INSTANCE;
- }
- }
附2 序列化(Serialization)导致的单例失效
- import java.io.FileInputStream;
- import java.io.FileOutputStream;
- import java.io.IOException;
- import java.io.ObjectInput;
- import java.io.ObjectInputStream;
- import java.io.ObjectOutput;
- import java.io.ObjectOutputStream;
- import java.io.OutputStream;
- public class SingletonSerializedTest {
- public static void main(String[] args) throws Exception {
- NestedSingleton nestedSingleton0 = NestedSingleton.getInstance();
- ObjectOutput output =null;
- OutputStream outputStream = new FileOutputStream("serializedFile.ser");
- output = new ObjectOutputStream(outputStream);
- output.writeObject(nestedSingleton0);
- output.close();
- ObjectInput input = new ObjectInputStream(new FileInputStream("serializedFile.ser"));
- NestedSingleton nestedSingleton1 = (NestedSingleton) input.readObject();
- input.close();
- System.out.println("nestedSingleton0 hashCode="+nestedSingleton0.hashCode());
- System.out.println("nestedSingleton1 hashCode="+nestedSingleton1.hashCode());
- }
- }
输出结果
- nestedSingleton0 hashCode=865113938
- nestedSingleton1 hashCode=2003749087
显然,产生了两个实例
如果要避免这个情况,则需要利用ObjectInputStream.readObject()中的机制,它在调用readOrdinaryObject()后会判断类中是否有ReadResolve()方法,如果有就采用类中的ReadResolve()新建实例
那么以上单例类就可以如下改造
- import java.io.Serializable;
- public class NestedSingleton implements Serializable {
- /**
- *
- */
- private static final long serialVersionUID = 3934012982375502226L;
- private NestedSingleton() {
- synchronized (NestedSingleton.class) {
- //判断是否已有实例
- if(SingletonClassHolder.SINGLE_INSTANCE != null){
- throw new RuntimeException("new another instance!");
- }
- }
- }
- //静态内部类,只初始化一次
- private static class SingletonClassHolder {
- static final NestedSingleton SINGLE_INSTANCE = new NestedSingleton();
- }
- //调用静态内部类方法得到单例
- public static NestedSingleton getInstance() {
- return SingletonClassHolder.SINGLE_INSTANCE;
- }
- public void printFn() {
- // TODO Auto-generated method stub
- System.out.print("fine");
- }
- protected Object readResolve() {
- return getInstance();
- }
- }
再次使用序列化反序列化过程验证,得到
- nestedSingleton0 hashCode=865113938
- nestedSingleton1 hashCode=865113938
这样在序列化反序列化过程保证了单例的实现
Java Singleton(单例模式) 实现详解的更多相关文章
- Java编程配置思路详解
Java编程配置思路详解 SpringBoot虽然提供了很多优秀的starter帮助我们快速开发,可实际生产环境的特殊性,我们依然需要对默认整合配置做自定义操作,提高程序的可控性,虽然你配的不一定比官 ...
- Java 8 Stream API详解--转
原文地址:http://blog.csdn.net/chszs/article/details/47038607 Java 8 Stream API详解 一.Stream API介绍 Java8引入了 ...
- java反射机制深入详解
java反射机制深入详解 转自:http://www.cnblogs.com/hxsyl/archive/2013/03/23/2977593.html 一.概念 反射就是把Java的各种成分映射成 ...
- 国际化,java.util.ResourceBundle使用详解
java.util.ResourceBundle使用详解 一.认识国际化资源文件 这个类提供软件国际化的捷径.通过此类,可以使您所编写的程序可以: 轻松地本地化或翻译成不同的 ...
- java之StringBuffer类详解
StringBuffer 线程安全的可变字符序列. StringBuffer源码分析(JDK1.6): public final class StringBuffer extends Abstract ...
- java.util.ResourceBundle使用详解
java.util.ResourceBundle使用详解 一.认识国际化资源文件 这个类提供软件国际化的捷径.通过此类,可以使您所编写的程序可以: 轻松地本地化或翻译成不同的 ...
- java之AbstractStringBuilder类详解
目录 AbstractStringBuilder类 字段 构造器 方法 public abstract String toString() 扩充容量 void expandCapacity(in ...
- java之StringBuilder类详解
StringBuilder 非线程安全的可变字符序列 .该类被设计用作StringBuffer的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍).如果可能,建议优先采用该类,因为在 ...
- java.util.ResourceBundle使用详解(转)
java.util.ResourceBundle使用详解 一.认识国际化资源文件 这个类提供软件国际化的捷径.通过此类,可以使您所编写的程序可以: 轻松地本地化或翻译成不同的 ...
随机推荐
- odoo:开源ERP/安装和初始设置
1.1 Odoo的结构 Odoo使用Web浏览器来访问Odoo服务,因此你的Odoo服务器可以部署在较远的地方(如另外一个城市),用户的计算机上只需安装谷歌.火狐或 IE9 以上的浏览器,所以Web客 ...
- 雨天的尾巴(bzoj3307)(线段树合并+树上差分)
\(N\)个点,形成一个树状结构.有\(M\)次发放,每次选择两个点\(x,y\) 对于\(x\)到\(y\)的路径上(含\(x,y\))每个点发一袋\(Z\)类型的物品.完成 所有发放后,每个点存放 ...
- MySQL(分组、连表操作、备份数据库)
day58 分组 参考:https://www.cnblogs.com/xp796/p/5262187.html select dept, max(salary) from department gr ...
- echart 遇到的点
1,图表随着外部container变化而变化: window.onresize = myChart.resize (拿着resize在api文档中搜就看到了)
- 缓存行和cpu缓存实例
并发框架Disruptor译文 剖析Disruptor:为什么会这么快?(一)锁的缺点 剖析Disruptor:为什么会这么快?(二)神奇的缓存行填充 剖析Disruptor:为什么会这么快?(三)伪 ...
- python pip安装模块提示错误failed to create process
python pip安装模块提示错误failed to create process 原因: 报这个错误的原因,是因为python的目录名称或位置发生改动. 解决办法: 1.找到修改python所在的 ...
- thuwc2019总结
275,是我的自己的估分 而350,是面试线 就发挥而言,这次的发挥相当糟糕,第一天选择全场打暴力而不打签到题正解,第二天因A题思路想偏造成2h额外时间花费.第二题与第三题之间,我选择了难打的第三题而 ...
- LeetCode All in One题解汇总(持续更新中...)
突然很想刷刷题,LeetCode是一个不错的选择,忽略了输入输出,更好的突出了算法,省去了不少时间. dalao们发现了任何错误,或是代码无法通过,或是有更好的解法,或是有任何疑问和建议的话,可以在对 ...
- Vue.js系列之二Vue实例
每个Vue应用都是通过Vue函数创建一个新的Vue实例开始,代码如下: var vm=new Vue({}); {}是创建Vue应用时的参数对象 1.Vue实例的data属性 当一个Vue对象被创建时 ...
- Vue的实时时间转换Demo
Vue的实时时间转换Demo time.html: <!DOCTYPE html> <html lang="en"> <head> <me ...