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

  • 对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销。
  • 由于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 disabled alert function in javascript

    how to disabled alert function in javascript alert 阻塞主线程 default alert; // ƒ alert() { [native code] ...

  2. js & h5 & app & object storage

    js & h5 & app & object storage API https://developer.mozilla.org/en-US/docs/Web/API Stor ...

  3. vue router & query params

    vue router & query params vue router get params from url https://zzk.cnblogs.com/my/s/blogpost-p ...

  4. rxjs 常用的subject

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

  5. 整合mybatis plus

    第一步:导入jar包 导入页面模板引擎,这里我们用的是freemarker <!--mp--> <dependency> <groupId>com.baomidou ...

  6. JUnit5学习之四:按条件执行

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  7. 微信小程序:自定义组件

    为什么要学习自定义组件? 1.用上我自己的单词abc,我希望在页面中展示椭圆形的图片, 2.打开手机淘宝,假如现在要做一个企业级项目,里面有很多页面,首页存在导航模块,点击天猫,进入第二个页面,而第二 ...

  8. 解决浏览器点击button出现边框问题

    发现问题 本人不懂浏览器的HTML代码 不知道怎么在chrome浏览器的F12之后点到了哪里 点击button的时候就会出现黑色边框 解决 终于发现不是因为动了调试页面,而是动了谷歌浏览器的高级选项, ...

  9. 如何进BAT,有了这个篇面试秘籍,成功率高达80%!!(附资料)

    多年前自己刚来北京找工作的时候,面了一个星期 面了七八家公司才拿到一个offer.而上次跳槽面了不到10家公司基本全过而且都给到了期望的薪资,本来自己在面试前没想到能够这么顺利,回想起来还是自己准备的 ...

  10. Cloud Alibabab笔记问世,全网详解仅此一份手慢无

    转: Cloud Alibabab笔记问世,全网详解仅此一份手慢无 什么是Spring cloud alibaba Spring Cloud Alibaba 是阿里巴巴提供的微服务开发一站式解决方案, ...