单例模式是设计模式中使用最为普遍的一种模式。属于对象创建模式,它可以确保系统中一个类只产生一个实例。这样的行为能带来两大好处:

  • 对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销。
  • 由于new操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻GC压力,缩短GC停顿时间。

在实际应用中,很多时候有一些对象我们只需要一个,例如:线程池(threadpool)、缓存(cache)、注册表(registry)、日志对象等等,这个时候把它设计为单例模式是最好的选择。

1、单例模式6种实现方法

1)懒汉模式(线程不安全)

public class Singleton01 {

    private static Singleton01 instance;

    /**
* 私有构造方法
*/
private Singleton01(){} public static Singleton01 getInstance() {
if(instance == null) {
instance = new Singleton01();
} return instance;
}
}

这种写法实现延迟加载,但线程不安全。禁止使用!

2)懒汉模式(线程安全)

public class Singleton02 {

    private static Singleton02 instance;

    /**
* 私有构造方法
*/
private Singleton02(){} public static synchronized Singleton02 getInstance() {
if(instance == null) {
instance = new Singleton02();
} return instance;
}
}

这种写法实现延迟加载,且增加synchronized来保证线程安全,但效率太低。不建议使用

3)懒汉模式(双重校验锁)

public class Singleton03 {

    private volatile static Singleton03 instance;

    /**
* 私有构造方法
*/
private Singleton03(){} public static Singleton03 getInstance() {
if(instance == null) {
synchronized (Singleton03.class) {
if (instance == null) {
instance = new Singleton03();
}
}
} return instance;
}
}

使用到了volatile机制。这个是第二种方式的升级版,俗称双重检查锁定。既保证了效率,又保证了安全。

4)饿汉模式

public class Singleton03 {

    private static Singleton03 instance = new Singleton03();

    /**
* 私有构造方法
*/
private Singleton03(){} public static synchronized Singleton03 getInstance() {
return instance;
}
}

这种基于类加载机制避免了多线程的同步问题,初始化的时候就给装载了。但却没了懒加载的效果。

这也是最简单的一种实现。

5)静态内部类

public class Singleton04 {

    // 静态内部类
private static class SingletonHolder {
private static final Singleton04 INSTANCE = new Singleton04();
} /**
* 私有构造方法
*/
private Singleton04(){} public static Singleton04 getInstance() {
return SingletonHolder.INSTANCE;
}
}

这种方式当Singleton04类被加载时,其内部类并不会被加载,所以单例类INSTANCE不会被初始化。

只有显式调用getInstance方法时,才会加载SingletonHolder,从而实例化INSTANCE

由于实例的建立是在类加载时完成,所以天生线程安全。因此兼备了懒加载和线程安全的特性。

6)枚举(号称最好)

public enum EnumSingleton01 {

    INSTANCE;

    public void doSomething() {
System.out.println("doSomething");
}
}

模拟数据库链接:

public enum EnumSingleton02 {

    INSTANCE;

    private DBConnection dbConnection = null;

    private EnumSingleton02() {
dbConnection = new DBConnection();
} public DBConnection getConnection() {
return dbConnection;
}
}

这种方式是Effective Java作者Josh Bloch提倡的方式,它不仅能避免多线程同步问题,

而且还能防止反序列化重新创建新的对象。

2、为什么说枚举方法是最好的?

前5种方式实现单例都有如下3个特点:

  • 构造方法私有化
  • 实例化的变量引用私有化
  • 获取实例的方法共有

首先,私有化构造器并不保险。因为它抵御不了反射攻击,其次就是序列化重新创建新对象。下面来进行验证。

1) 反射验证

@Test
public void reflectTest() throws Exception {
Singleton03 s = Singleton03.getInstance(); // 拿到所有的构造函数,包括非public的
Constructor<Singleton03> constructor = Singleton03.class.getDeclaredConstructor();
constructor.setAccessible(true); // 构造实例
Singleton03 reflection = constructor.newInstance(); System.out.println(s);
System.out.println(reflection);
System.out.println(s == reflection);
}

输出结果:

org.yd.singleton.Singleton03@61e4705b
org.yd.singleton.Singleton03@50134894
false

再看看枚举类的测试

@Test
public void reflectEnumTest() throws Exception {
EnumSingleton01 s = EnumSingleton01.INSTANCE; // 拿到所有的构造函数,包括非public的
Constructor<EnumSingleton01> constructor = EnumSingleton01.class.getDeclaredConstructor();
constructor.setAccessible(true); // 构造实例
EnumSingleton01 reflection = constructor.newInstance(); System.out.println(s);
System.out.println(reflection);
System.out.println(s == reflection);
}

输出结果:

java.lang.NoSuchMethodException: org.yd.singleton.EnumSingleton01.<init>()
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.getDeclaredConstructor(Class.java:2178)
at org.yd.singleton.SingletonTest.reflectEnumTest(SingletonTest.java:61)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

结论:通过反射,单例模式的私有构造方法也能构造出新对象。不安全。而枚举类直接抛异常,说明枚举类对反射是安全的。

2) 序列化验证

@Test
public void serializeTest(){
Singleton03 s = Singleton03.getInstance(); String serialize = JSON.toJSONString(s);
Singleton03 deserialize =JSON.parseObject(serialize,Singleton03.class); System.out.println(s);
System.out.println(deserialize);
System.out.println(s == deserialize); }

输出结果:

org.yd.singleton.Singleton03@387c703b
org.yd.singleton.Singleton03@75412c2f
false

结论:序列化前后两个对象并不相等。所以序列化也是不安全的。

同样看看枚举类的测试

@Test
public void serializeEnumTest(){
EnumSingleton01 s = EnumSingleton01.INSTANCE; String serialize = JSON.toJSONString(s);
EnumSingleton01 deserialize =JSON.parseObject(serialize,EnumSingleton01.class); System.out.println(s);
System.out.println(deserialize);
System.out.println(s == deserialize);
}

输出结果:

INSTANCE
INSTANCE
true

结论:说明枚举类序列化安全。


综上,可以得出结论:枚举是实现单例模式的最佳实践。

  • 反射安全
  • 序列化/反序列化安全
  • 写法简单

Java单例模式实现,一次性学完整,面试加分项的更多相关文章

  1. 【面试加分项】java自己定义注解之申明注解

    之前的博客http://blog.csdn.net/u010590685/article/details/47029447介绍了java的注解的基本知识今天我们学习怎样使用自己定义注解. 首先我们要声 ...

  2. 【面试加分项】java自己定义注解之解析注解

    我之前的博客中说明过自己定义注解的声明今天我们来看看怎样对我们自己定义的注解进行使用. 1.我们在程序中使用我们的注解. 上一篇中我们自己定义了一个注解: @Target(ElementType.FI ...

  3. 「美团面试系列」面试加分项,这样说你会JVM,面试官还能问什么

    Java性能调优都是老生常谈的问题,特别当“糙快猛”的开发模式大行其道时,随着系统访问量的增加.代码的臃肿,各种性能问题便会层出不穷. 比如,下面这些典型的性能问题,你肯定或多或少都遇到过: 在进行性 ...

  4. 面试加分项-HashMap源码中这些常量的设计目的

    前言 之前周会技术分享,一位同事讲解了HashMap的源码,涉及到一些常量设计的目的,本文将谈谈这些常量为何这样设计,希望大家有所收获. HashMap默认初始化大小为什么是1 << 4( ...

  5. 你真的理解了java单例模式吗?讲别人都忽略的细节!

    前言:老刘这篇文章敢做保证,java的单例模式讲的比大多数的技术博客都要好,讲述别人技术博客都没有的细节!!! 1 java单例模式 直接讲实现单例模式的两种方法:懒汉式和饿汉式,单例模式的概念自己上 ...

  6. 【深入】java 单例模式(转)

    [深入]java 单例模式 关于单例模式的文章,其实网上早就已经泛滥了.但一个小小的单例,里面却是有着许多的变化.网上的文章大多也是提到了其中的一个或几个点,很少有比较全面且脉络清晰的文章,于是,我便 ...

  7. 深入Java单例模式(转)

    深入Java单例模式 源自 http://devbean.blog.51cto.com/448512/203501 在GoF的23种设计模式中,单例模式是比较简单的一种.然而,有时候越是简单的东西越容 ...

  8. Java程序员普遍存在的面试问题以及应对之道(新书第一章节摘录)

    其实大多数Java开发确实能胜任日常的开发工作,但不少候选人却无法在面试中打动面试官.因为要在短时间的面试中全面展示自己的实力,这很需要技巧,而从当前大多数Java开发的面试现状来看,会面试的候选人不 ...

  9. java开发都需要学什么

    1.java基础 2.JSP+Servlet+JavaBean 环节主要 懂流程 MVC而已 别往深了研究 现 开发基本 用 模式 3.Struts+Hibernate+Spring 才 开发 主流技 ...

随机推荐

  1. js & array & shuffle

    js & array & shuffle const list = [1, 2, 3, 4, 5, 6, 7, 8, 9]; list.sort(() => Math.rando ...

  2. Teadocs & markdown website

    Teadocs & markdown website Teadocs 是一款能够帮你快速构建html文档的工具,它基于nodejs编写,并使用markdown来编写文档内容. Teadocs ...

  3. nodejs 调用win32 api

    video 教程文件 win32 api >node -v v12.16.1 >npm install -g node-gyp >npm i @saleae/ffi >node ...

  4. apollo-server 返回模拟数据

    模式模拟GraphQL数据 const { ApolloServer, gql } = require('apollo-server'); const typeDefs = gql` type Que ...

  5. 1月22日第二轮空投来袭,SPC算力福利币究竟能带来什么?

    行情数据显示,比特币于14日23时30分再次突破40000美元,市值回升至7400亿美元.根据行情频道数据,比特币于14日2时展露上行态势,价格于34000美元附近起跳,至12时站上37000美元.此 ...

  6. 源码分析:Phaser 之更灵活的同步屏障

    简介 Phaser 是 JDK 1.7 开始提供的一个可重复使用的同步屏障,功能类似于CyclicBarrier和CountDownLatch,但使用更灵活,支持对任务的动态调整,并支持分层结构来达到 ...

  7. python进阶(6)深拷贝和浅拷贝

    深拷贝和浅拷贝 不管对于浅拷贝.还是深拷贝,针对不可变对象str.int.tuple(有点特殊).boolean,它的内存地址是不变的,拷贝的仅仅是值 import copy a = 1 b = co ...

  8. Linux graphics stack

    2D图形架构 早期Linux图形系统的显示全部依赖X Server,X Client调用Xlib提供的借口向 X Server发送渲染命令,X Server根据 X Client的命令请求向硬件设备绘 ...

  9. AXU2CGB开发板验证Vitis加速基本平台创建

    Vitis 加速基本平台创建 1.Vivado 工程创建,硬件平台bd 图如下所示 1.1.双击Block图中ZYNQ核,配置相关参数 1.1.1.Low Speed 配置,在 I/O Configu ...

  10. MySQL:多表查询

    SELECT查询不但可以从一张表查询数据,还可以从多张表同时查询数据.查询多张表的语法是:SELECT * FROM <表1> <表2>,普通多表查询会获取M x N行记录,所 ...