第一次总结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高级特性与最佳实践》

以及各种网络博客

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

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