第一次总结Java单例

老大让写一篇最优单例跟大家分享一下、当然你们是了解我的、肯定是嘴上说着不要、身体却不由自主~

那什么是最优单例呢、现在我们得先了解一下什么是单例

1.到底什么是单例?

我们都知道Java虚拟机内存模型分为堆栈方法区、是下面这个样子

我们平时关心的也就只有 堆、单例是一种设计模式、就是通过代码手段来保证系统中类的实例(也就是在堆中分配的空间)仅有一个。

2、单例的使用场景

   什么场景下使用单例呢?~ 

2.1.当系统只需要唯一一个资源的时候使用单例、例如:配置文件的加载 

2.2 当系统中的资源创建比较消耗资源时、使用单例模式创建缓冲区、例如:数据库链接池  我们使用单例模式将需要的链接对象缓存起来(有人管这个叫多例)、将链接对象缓存起来、使用时无需再创建链接即可使用

3、单例的种类   

3.1.饿汉式:   

代码:

public class ClassA{

private ClassA(){}

private static ClassA classA = new ClassA();

public static ClassA getInstance(){

return classA;

}

}

3.1.1 一言不合将构造方法私有     

3.1.2 一言不合创建一个内部静态本类对象实例属性     

3.1.3 构造一个静态方法返回

3.1.2中构造的静态属性     

这种方式在类进行加载的时候便会初始化并分配内存空间、为了节省空间推出了懒汉式。   

3.2.懒汉式:     

代码:

public class ClassB {

private ClassB(){}

private static ClassB classB;

public static ClassB getInstance(){

if(classB == null){

classB = new ClassB();

}

return classB;

}

}

3.2.1 同样将构造方法私有化     

3.2.2 声明静态本类对象属性、然而不进行实例化(然而会分配空间)     

3.2.3 声明公有的方法,返回本类的实例对象(未初始化的时候需要初始化)     

以上、懒汉式实现也很简单、和饿汉式的区别仅仅在于是否是开始的时候进行实例化、然而这种懒汉式会产生多线程的并发问题、也就是在多线程环境并发访问时可能会实例化不同的实例对象、为了解决这种问题所以我们需要进行控制--加锁。   

3.3懒汉式加锁   

代码:

public class ClassC {

private ClassC(){}

private static ClassC classC;

public static ClassC getInstance(){

if(classC == null){

synchronized (ClassC.class){

if(classC == null){

classC = new ClassC();

}

}

}

return classC;

}

}

3.3.1 同样将构造方法私有化     

3.3.2 声明静态本类对象属性、然而不进行实例化

3.3.3 在对象未实例化的时候判断加锁、如果不为空直接返回    这种类型解决了多线程访问和延迟加载的问题、这就是最优的单例了么?   

我们现在总结一下设计单例过程中遇到的问题、1. 饿汉:未使用单例时便分配了内存、占用空间;2.懒汉:会出现多线程并发问题、我们通过加入同步锁解决了、那有没有一种设计不但不在未使用的情况占用空间、不需要额外加锁或者加的锁的效率是最高的么,我们接下来看下内部类实现的单例模式。   

3.4 内部类单例   

在准备这篇文章之前我是从来没有听说过内部单例模式的,为了验证类的加载顺序我加入了打印日志、

先上代码

public class ClassD {

private static class InnerClassD{

private static ClassD instance = new ClassD();

static{

System.out.println("InnerClassD");

}

public static ClassD getInstance(){

System.out.println("InnerClassD method run.");

return instance;

}

}

public static int TEST = 1;

private ClassD(){

}

public static void main(String[] args){

System.out.println("ClassD before");

System.out.println("ClassD.TEST=" + ClassD.TEST);

System.out.println("ClassD after");

ClassD classD1 = ClassD.InnerClassD.getInstance();

}

}

执行结果:

ClassD before

ClassD.TEST=1

ClassD after

InnerClassD

InnerClassD method run.

从打印结果我们可以看出、类ClassD在被加载后、静态内部类InnerClassD并没有被加载、直到我们调用内部类的方法时,内部类才被加载,由此可实现单例的延迟加载; 当内部类方法被调用时初始化静态ClassD的成员变量、由于类加载的过程是由JVM来保证的线程安全、由此来看、这种通过内部类实现的单例与之前的饿汉懒汉相比更技高一筹。   

3.5写到这里我们似乎已经找到了最优单例的实现、然而、如果考虑Java反射更改类的构造方法的权限、以及序列化创建对象实例的情况、便会破坏单例的实例只有一个的特性。         

//反射以及序列化生成多个对象

public final class ClassE implements Serializable{   

private static class InnerClassE{       

private static ClassE instance = new ClassE();       

static{           

System.out.println("InnerClassE");       

}       

public static ClassE getInstance(){           

System.out.println("InnerClassE method run.");           

return instance;       

}   

}   

public static int TEST = 1;   

private ClassE(){    }   

public static void main(String[] args) throws NoSuchMethodException, IOException, ClassNotFoundException, IllegalAccessException, InvocationTargetException, InstantiationException {       

ClassE instance = InnerClassE.getInstance();       

ConstructordeclareMethod = null;

ClassE instance2 = instance;

ClassE instance3 = instance;

declareMethod = ClassE.class.getDeclaredConstructor(new Class[]{});

ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("classE.txt"));

objectOutputStream.writeObject(instance);

ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("classE.txt"));

instance3 = (ClassE) objectInputStream.readObject();

instance2 = declareMethod.newInstance();

System.out.println(instance == instance2);

System.out.println(instance == instance3);

}

}

打印结果:

InnerClass

InnerClassE method run.

false

false

由此我们看两种方法出生成的新的实例对象均为新的堆内地址、为了保证单堆内存的唯一、我们需要禁用反射、因为枚举类型不能通过反射来生成新的实例、由枚举来创建单例就成为了更优于前面几种实现的单例了。

枚举类型不能通过反射来生成新的实例、Constructor类的 newInstance 方法

if ((clazz.getModifiers() & Modifier.ENUM) != 0)

throw new IllegalArgumentException("Cannot reflectively create enum objects");

ConstructorAccessor ca = constructorAccessor;  // read volatile

从代码我们可以看出如果使用反射的方式生成枚举的实例对象将会抛出运行时异常("Cannot reflectively create enum objects" )

当枚举进行序列化与反序列化时、反序列化生成的对象与被序列化的对象为同一对象、保证了实例的唯一性。

代码:

ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("classG.txt"));

outputStream.writeObject(ClassG.HELLOWROLD);

ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("classG.txt"));

Object object = inputStream.readObject();

ClassG classG = (ClassG) object;

System.out.println(classG == ClassG.HELLOWROLD);

执行结果:

true

最后、借鉴Effective Java中的一段话、最优单例是由枚举实现的。

参考文档:《Effective Java中文版 第2版》 .(Joshua Bloch).[PDF]&ckook

《深入理解Java虚拟机-JVM高级特性与最佳实践》

以及各种网络博客

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,427评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,551评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,747评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,939评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,955评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,737评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,448评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,352评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,834评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,992评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,133评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,815评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,477评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,022评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,147评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,398评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,077评论 2 355

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,672评论 18 139
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,644评论 18 399
  • 小编费力收集:给你想要的面试集合 1.C++或Java中的异常处理机制的简单原理和应用。 当JAVA程序违反了JA...
    八爷君阅读 4,594评论 1 114
  • 1 场景问题# 1.1 读取配置文件的内容## 考虑这样一个应用,读取配置文件的内容。 很多应用项目,都有与应用相...
    七寸知架构阅读 6,775评论 12 68
  • 最安全的方法就是,找到警告的位置直接修改。 这是方法是最好的的,也是最安全的。但是有的时候,确实会出现一下不可避免...
    XPorter阅读 1,517评论 0 0