Java单例模式是最常见的设计模式之一,广泛应用于各种框架、中间件和应用开发中。单例模式实现起来比较简单,基本是每个Java工程师都能信手拈来的,本文将结合多线程、类的加载等知识,系统地介绍一下单例模式的演变,并体现在7种不同的单例设计中。说到这个,非常像孔乙己里那个“回字有四种写法”的梗,不过与封建迂腐文人不同的是,从简单的单例设计变化,可以看到一个需求演变的过程,看到一个方法不断完善的过程。

传送门:Java并发编程中的设计模式解析(一)

1. 饿汉式

最简单的单例设计,优点是线程安全,但是因为类加载即初始化实例,加入实例变量比较多的话,会占用较多的内存。

 //不允许被继承
public final class SingletonStarve {
//实例变量, 由于单例对象是静态的, 在类的加载阶段, 就会初始化实例变量
@SuppressWarnings("unused")
private byte[] data = new byte[1024];
//定义静态实例对象的时候直接初始化
private static SingletonStarve instance = new SingletonStarve();
//私有化构造函数, 不允许直接new对象
private SingletonStarve() {}
//提供公共的方法获取实例对象
public static SingletonStarve getInstance() {
return instance;
}
}

2. 懒汉式

实现了单例设计的懒加载,节省了前期内存空间的占用,但是在多线程环境下可能会导致多对象的产生,破坏实例唯一性。

 //不允许被继承
public final class LazySingleton {
//实例变量, 由于单例对象是静态的, 在类的加载阶段, 就会初始化实例变量
@SuppressWarnings("unused")
private byte[] data = new byte[1024];
//定义静态实例对象, 不直接初始化
private static LazySingleton instance = null;
//私有化构造函数, 不允许直接new对象
private LazySingleton() {}
//提供公共的方法获取实例对象
public static LazySingleton getInstance() {
if(null == instance) {
instance = new LazySingleton();
}
return instance;
}
}

3. 懒汉式+同步锁

通过使用synchronized关键字使getInstance方法变为同步方法,从而确保线程安全,但带来了一定的性能问题。

 //不允许被继承
public final class SyncLazySingleton {
//实例变量, 由于单例对象是静态的, 在类的加载阶段, 就会初始化实例变量
@SuppressWarnings("unused")
private byte[] data = new byte[1024];
//定义静态实例对象, 不直接初始化
private static SyncLazySingleton instance = null;
//私有化构造函数, 不允许直接new对象
private SyncLazySingleton() {}
//提供公共的方法获取实例对象, 通过synchronized修饰为同步方法
public static synchronized SyncLazySingleton getInstance() {
if(null == instance) {
instance = new SyncLazySingleton();
}
return instance;
}
}

4. Double-Check

推荐使用:Double-Check单例模式,通过两次非空判断,并且对第二次判断加锁,确保了多线程下的单例设计安全,同时保证了性能。

注意:Double-check有可能因为JVM指令重排的原因,导致空指针异常;使用volatile修饰对象引用,可以确保其可见性,避免异常

 //不允许被继承
public final class VolatileDoubleCheckSingleton {
//实例变量, 由于单例对象是静态的, 在类的加载阶段, 就会初始化实例变量
@SuppressWarnings("unused")
private byte[] data = new byte[1024];
//定义静态实例对象, 不直接初始化
//通过volatile, 避免指令重排序导致的空指针异常
private static volatile VolatileDoubleCheckSingleton instance = null;
Connection conn;
Socket socket;
//私有化构造函数, 不允许直接new对象
//由于指令重排序, 实例化顺序可能重排, 从而导致空指针,使用volatile关键字修饰单例解决
private VolatileDoubleCheckSingleton() {
//this.conn;
//this.socket;
}
//提供公共的方法获取实例对象
public static VolatileDoubleCheckSingleton getInstance() {
if(null == instance) {
synchronized(VolatileDoubleCheckSingleton.class) {
if(null == instance) {//以下赋值因为不是原子性的,如果不使用volatile使instance在多个线程中可见,将可能导致空指针
instance = new VolatileDoubleCheckSingleton();
}
}
}
return instance;
}
}

5. 静态内部类

推荐使用:通过使用静态内部类,巧妙地避免了线程不安全,并且节省了前期内存空间,编码非常简洁。

 //不允许被继承
public final class HolderSingleton {
//实例变量
@SuppressWarnings("unused")
private byte[] data = new byte[1024];
//私有化构造器
private HolderSingleton() {}
//定义静态内部类Holder, 及内部实例成员, 并直接初始化
private static class Holder{
private static HolderSingleton instance = new HolderSingleton();
}
//通过Holder.instance获得单例
public static HolderSingleton getInstance() {
return Holder.instance;
}
}

6. 枚举类

《Effective Java》中推荐的单例设计模式,缺点是饿汉式,并且对编码能力要求较高。

 //枚举本身是final的, 不允许被继承
public enum EnumSingleton {
INSTANCE;
//实例变量
@SuppressWarnings("unused")
private byte[] data = new byte[1024]; EnumSingleton() {
System.out.println("INSTANCE will be initialized immediately");
}
public static void method() {
//调用该方法会主动使用EnumSingleton, INSTANCE将会实例化
}
public static EnumSingleton getInstance() {
return INSTANCE;
}
}

7. 内部枚举类

 /*
* 使用枚举类作为内部类实现懒加载
*/
public final class LazyEnumSingleton {
private LazyEnumSingleton(){}
private enum EnumHolder{
INSTANCE;
private LazyEnumSingleton instance;
EnumHolder(){
this.instance = new LazyEnumSingleton();
}
private LazyEnumSingleton getLazyEnumSingleton() {
return instance;
}
}
public static LazyEnumSingleton getInstance() {
return EnumHolder.INSTANCE.getLazyEnumSingleton();
}
}

Java并发编程中的设计模式解析(二)一个单例的七种写法的更多相关文章

  1. Java并发编程中的设计模式解析(一)

    Java并发编程,除了被用于各种Web应用.分布式系统和大数据系统,构成高并发系统的核心基础外,其本身也蕴含着大量的设计模式思想在里面.这一系列文章主要是结合Java源码,对并发编程中使用到的.实现的 ...

  2. Java并发编程:volatile关键字解析

    Java并发编程:volatile关键字解析 volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在 ...

  3. (转)Java并发编程:volatile关键字解析

    转:http://www.cnblogs.com/dolphin0520/p/3920373.html Java并发编程:volatile关键字解析 volatile这个关键字可能很多朋友都听说过,或 ...

  4. Java并发编程:volatile关键字解析(转载)

    转自https://www.cnblogs.com/dolphin0520/p/3920373.html Java并发编程:volatile关键字解析   Java并发编程:volatile关键字解析 ...

  5. Java并发编程:volatile关键字解析-转

    Java并发编程:volatile关键字解析 转自海子:https://www.cnblogs.com/dayanjing/p/9954562.html volatile这个关键字可能很多朋友都听说过 ...

  6. 6、Java并发编程:volatile关键字解析

    Java并发编程:volatile关键字解析 volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在 ...

  7. 转:Java并发编程:volatile关键字解析

    Java并发编程:volatile关键字解析 Java并发编程:volatile关键字解析 volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字, ...

  8. [转载]Java并发编程:volatile关键字解析

    Java并发编程:volatile关键字解析 volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在 ...

  9. Java并发编程中的若干核心技术,向高手进阶!

    来源:http://www.jianshu.com/p/5f499f8212e7 引言 本文试图从一个更高的视角来总结Java语言中的并发编程内容,希望阅读完本文之后,可以收获一些内容,至少应该知道在 ...

随机推荐

  1. 2_C语言中的数据类型 (十)while、for

    1          循环语句 1.1       while while(条件),如果条件为真,循环继续,条件为假,循环结束 while (1)..是死循环的写法 1.2       continu ...

  2. Python之元类详细解析

    一.补充内置函数isinstance和issubclass 1.isinstance是判断一个对象是不是由一个对象产生的 class Foo: pass obj=Foo() print(isinsta ...

  3. flag -- 诡异的memcache标记

    引子     打从去年一路北漂,进入无人货架行业,业务需求漫天飘,最近总算把工作都规划齐整.回望过去一年多的时间里,诸多东西值得整理,memcache就是其中一个.  看到java的工资高些,队伍中好 ...

  4. C#课后练手

    猜拳(三局两胜)请输入您的手势:石头用户手势:石头      电脑手势:剪刀用户胜:1      电脑胜:0 请输入您的手势:石头用户手势:石头      电脑手势:石头用户胜:1      电脑胜: ...

  5. ORM框架学习之EF

    首先推荐一篇很好的EF文章翻译,可以系统的学习一遍. <Entity Framework 6 Recipes>中文翻译系列 EF使用体会 优点: 可以省去Ado.net复杂的管道连接代码. ...

  6. Direct2D处理几何图形之间的碰撞检测(下)

    转载请注明出处:http://www.cnblogs.com/Ray1024 一.概述 上一篇文章中我们介绍了几何图形与点的碰撞检测.几何图形与点的位置关系比较简单:点在几何图形内.点在几何图形外.点 ...

  7. elk6.3 centos集群搭建 head插件安装

    版本elk均为6.3+centos7.0 准备工作 官网下载elk6.3的linux环境的压缩包,sftp上传 下载对应的head插件sftp上传到指定目录 tar.gz文件解压 tar -zxvf ...

  8. JMeter做http接口压力测试

    测前准备 用JMeter做接口的压测非常方便,在压测之前我们需要考虑这几个方面: 场景设定 场景分单场景和混合场景.针对一个接口做压力测试就是单场景,针对一个流程做压力测试的时候就是混合场景,需要多个 ...

  9. 互联网校招面试必备——Java多线程

    本文首发于我的个人博客:尾尾部落 本文是我刷了几十篇一线互联网校招java后端开发岗位的面经后总结的多线程相关题目,虽然有点小长,但是面试前看一看,相信能帮你轻松啃下多线程这块大骨头. 什么是进程,什 ...

  10. Netty源码分析第6章(解码器)---->第4节: 分隔符解码器

    Netty源码分析第六章: 解码器 第四节: 分隔符解码器 基于分隔符解码器DelimiterBasedFrameDecoder, 是按照指定分隔符进行解码的解码器, 通过分隔符, 可以将二进制流拆分 ...