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

  • 对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销。
  • 由于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. How to implement an accurate countdown timer with js

    How to implement an accurate countdown timer with js 如何用 js 实现一个精确的倒计时器 原理剖析 web worker js custom ti ...

  2. TypeScript 3.7 RC & Nullish Coalescing

    TypeScript 3.7 RC & Nullish Coalescing null, undefined default value https://devblogs.microsoft. ...

  3. input support upload excel only

    input support upload excel only demo https://codepen.io/xgqfrms/pen/vYONpLB <!-- <input placeh ...

  4. rxjs 常用的subject

    api列表 Subject Subject是可观察的一种特殊类型,它允许将值多播到许多观察者 import {Subject} from 'rxjs'; const l = console.log; ...

  5. VAST二月上线交易所,打通NGK各大币种之间通道!

    1月20日,管理着超过8.7万亿美元资产的全球最大资产管理公司贝莱德似乎已批准其旗下两个相关基金--贝莱德全球分配基金公司和贝莱德基金投资比特币期货.提交给美国证券交易委员会的招股说明书文件显示,贝莱 ...

  6. Mybatis-04 日志、分页

    Mybatis-04 日志.分页 日志 1.日志工厂 如果数据库操作出现异常,就需要打印日志来排错. 日志工厂会把日志工作委托实现: SLF4J Apache Commons Logging Log4 ...

  7. fork后子进程与父进程的关系

    共享代码空间,各自独立数据空间,子进程初始化数据是父进程的复制. 资料参考: https://blog.csdn.net/u013851082/article/details/76902046

  8. RocketMQ同一个消费者唯一Topic多个tag踩坑经历

    最近做的项目的一个版本需求中,需要用到MQ,对数据记录进行异步落库,这样可以减轻数据库的压力,同时可以抗住大量的数据落库.这里需要说明一下本人用到的MQ是公司自己在阿里的RokectMQ的基础上进行封 ...

  9. Docker Elasticsearch 集群配置

    一:选用ES原因 公司项目有些mysql的表数据已经超过5百万了,各种业务的查询入库压力已经凸显出来,初步打算将一个月前的数据迁移到ES中,mysql的老数据就物理删除掉. 首先是ES使用起来比较方便 ...

  10. Go的指针

    目录 指针 一.指针的声明 二.指针的默认值(Zero Value) 三.指针的解引用 四.向函数传递指针参数 1.非 数组/切片 指针传参 2.数组/切片 指针传参 五.Go不支持指针运算 指针 指 ...