什么是单例模式?

Intend:Ensure a class only has one instance, and provide a global point of access to it.

目标:保证一个类只有一个实例,并提供全局访问点

--------(《设计模式:可复用面向对象软件的基础》

就运行机制来说,就是一个类,在运行过程中只存在一份内存空间,外部的对象想使用它,都只会调用那部分内存。

其目的有实现唯一控制访问,节约资源,共享单例实例数据。

单例基础实现有两种思路,

1.Eager initialization:在加载类时构造;2.Lazy Initialization:在类使用时构造;3

1.Eager initialization适用于高频率调用,其由于预先创建好Singleton实例会在初始化时使用跟多时间,但在获得实例时无额外开销

其典型代码如下:

public class EagerInitSingleton {
//构建实例
private static final EagerInitSingleton SINGLE_INSTANCE = new EagerInitSingleton();
//私有化构造器
private EagerInitSingleton(){}
//获得实例
public static EagerInitSingleton getInstance(){
return SINGLE_INSTANCE;
}
}

换一种思路,由于类内静态块也只在类加载时运行一次,所以也可用它来代替构造单例:

public class EagerInitSingleton {
//构建实例
//private static final EagerInitSingleton instance = new EagerInitSingleton();
//此处不构造
private static StaticBlockSingleton instance; //使用静态块构造
static{
try{
instance = new StaticBlockSingleton();
}catch(Exception e){
throw new RuntimeException("Exception occured in creating singleton instance");
}
} //私有化构造器
private EagerInitSingleton(){}
//获得实例
public static EagerInitSingleton getInstance(){
return instance;
}
}

2.Lazy Initialization适用于低频率调用,由于只有使用时才构建Singleton实例,在调用时会有系列判断过程所以会有额外开销

2.1Lazy Initialization单线程版

其初步实现如下:

public class LazyInitSingleton {

    private static LazyInitSingleton SINGLE_INSTANCE = null;

    //私有化构造器
private LazyInitSingleton() {} //构造实例
public static LazyInitSingleton getInstance() {
if (SINGLE_INSTANCE == null) {
SINGLE_INSTANCE = new LazyInitSingleton();//判断未构造后再构造
}
return SINGLE_INSTANCE;
}
}

2.2Lazy Initialization多线程版

在多线程下,可能多个线程在较短时间内一同调用 getInstance()方法,且判断

SINGLE_INSTANCE == null

结果都为true,则2.1Lazy Initialization单线程版 会构造多个实例,即单例模式失效

作为修正

2.2.1synchronized关键字第一版

可考虑使用synchronized关键字同步获取方法

public class  LazyInitSingleton {

    private static  LazyInitSingleton SINGLE_INSTANCE = null;

    //私有化构造器
private LazyInitSingleton() {} //构造实例,加入synchronized关键字
public static synchronized LazyInitSingleton getInstance() {
if (SINGLE_INSTANCE == null) {
SINGLE_INSTANCE = new LazyInitSingleton();//判断未构造后再构造
}
return SINGLE_INSTANCE;
}
}

2.2.2synchronized关键字第二版(double checked locking 二次判断锁)

以上可实现线程安全,但由于使用了synchronized关键字实现锁定控制,getInstance()方法性能下降,造成瓶颈。分析到需求构建操作只限于未构建判断后第一次调用getInstance()方法,即构建为低频操作,所以完全可以在判断已经构建后直接返回,而不需要使用锁,仅在判断需要构建后才进行锁定:

public class  LazyInitSingleton {

    private static  LazyInitSingleton SINGLE_INSTANCE = null;

    //私有化构造器
private LazyInitSingleton() {} //构造实例
public static synchronized LazyInitSingleton getInstance() {
if (SINGLE_INSTANCE == null) {
synchronized(LazyInitSingleton.class){
if (SINGLE_INSTANCE == null) {
SINGLE_INSTANCE = new LazyInitSingleton();//判断未构造后再构造
}
}
}
return SINGLE_INSTANCE;
}
}

3利用静态内部类实现懒加载

JVM仅在使用时加载静态资源,当类加载时,静态内部类不会加载,仅当静态内部类在使用时会被加载,且实现顺序初始化即加载是线程安全的,利用这一性质,我们可以实现懒加载

 public class NestedSingleton {

    private NestedSingleton() {}
//静态内部类,只初始化一次
private static class SingletonClassHolder {
static final NestedSingleton SINGLE_INSTANCE = new NestedSingleton();
}
//调用静态内部类方法得到单例
public static NestedSingleton getInstance() {
return SingletonClassHolder.SINGLE_INSTANCE;
} }

4.使用Enum

由于枚举类是线程安全的,可以直接使用枚举创建单例。但考虑到枚举无法继承,所以只在特定情况下使用

public enum EnumSingleton {

    INSTANCE;
}

附1 利用反射机制破解单例(非Enum形式的单例,)

单例形式的类可被反射破解,从而使用单例失效,即将其Private构造器,通过反射形式暴露出来,并进行实例的构造

    public static void main(String[] args) {

        NestedSingleton nestedSingleton =  NestedSingleton.getInstance();
NestedSingleton nestedSingleton2 = null;
try {
//暴露构造器
Constructor[] constructors = nestedSingleton.getClass().getDeclaredConstructors();
Constructor constructor = constructors[1];
constructor.setAccessible(true);
nestedSingleton2 = (NestedSingleton)constructor.newInstance(); } catch (Exception e ) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(nestedSingleton.hashCode());
System.out.println(nestedSingleton2.hashCode());
}
}

为防止以上情况,可在构造器中抛出异常,以阻止新的实例产生

public class NestedSingleton {

    private NestedSingleton() {
synchronized (NestedSingleton.class) {
//判断是否已有实例
if(SingletonClassHolder.SINGLE_INSTANCE != null){
throw new RuntimeException("new another instance!");
}
} }
//静态内部类,只初始化一次
private static class SingletonClassHolder {
static final NestedSingleton SINGLE_INSTANCE = new NestedSingleton(); }
//调用静态内部类方法得到单例
public static NestedSingleton getInstance() {
return SingletonClassHolder.SINGLE_INSTANCE;
}
}

附2 序列化(Serialization)导致的单例失效

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.OutputStream; public class SingletonSerializedTest { public static void main(String[] args) throws Exception { NestedSingleton nestedSingleton0 = NestedSingleton.getInstance();
ObjectOutput output =null;
OutputStream outputStream = new FileOutputStream("serializedFile.ser");
output = new ObjectOutputStream(outputStream);
output.writeObject(nestedSingleton0);
output.close(); ObjectInput input = new ObjectInputStream(new FileInputStream("serializedFile.ser"));
NestedSingleton nestedSingleton1 = (NestedSingleton) input.readObject();
input.close(); System.out.println("nestedSingleton0 hashCode="+nestedSingleton0.hashCode());
System.out.println("nestedSingleton1 hashCode="+nestedSingleton1.hashCode()); }
}

输出结果

nestedSingleton0 hashCode=865113938
nestedSingleton1 hashCode=2003749087

显然,产生了两个实例

如果要避免这个情况,则需要利用ObjectInputStream.readObject()中的机制,它在调用readOrdinaryObject()后会判断类中是否有ReadResolve()方法,如果有就采用类中的ReadResolve()新建实例

那么以上单例类就可以如下改造

import java.io.Serializable;

public class NestedSingleton implements Serializable {

    /**
*
*/
private static final long serialVersionUID = 3934012982375502226L;
private NestedSingleton() {
synchronized (NestedSingleton.class) {
//判断是否已有实例
if(SingletonClassHolder.SINGLE_INSTANCE != null){
throw new RuntimeException("new another instance!");
}
}
}
//静态内部类,只初始化一次
private static class SingletonClassHolder {
static final NestedSingleton SINGLE_INSTANCE = new NestedSingleton();
}
//调用静态内部类方法得到单例
public static NestedSingleton getInstance() {
return SingletonClassHolder.SINGLE_INSTANCE;
}
public void printFn() {
// TODO Auto-generated method stub
System.out.print("fine");
}
protected Object readResolve() {
return getInstance();
}
}

再次使用序列化反序列化过程验证,得到

nestedSingleton0 hashCode=865113938
nestedSingleton1 hashCode=865113938

这样在序列化反序列化过程保证了单例的实现

Java Singleton(单例模式) 实现详解的更多相关文章

  1. Java编程配置思路详解

    Java编程配置思路详解 SpringBoot虽然提供了很多优秀的starter帮助我们快速开发,可实际生产环境的特殊性,我们依然需要对默认整合配置做自定义操作,提高程序的可控性,虽然你配的不一定比官 ...

  2. Java 8 Stream API详解--转

    原文地址:http://blog.csdn.net/chszs/article/details/47038607 Java 8 Stream API详解 一.Stream API介绍 Java8引入了 ...

  3. java反射机制深入详解

    java反射机制深入详解  转自:http://www.cnblogs.com/hxsyl/archive/2013/03/23/2977593.html 一.概念 反射就是把Java的各种成分映射成 ...

  4. 国际化,java.util.ResourceBundle使用详解

    java.util.ResourceBundle使用详解   一.认识国际化资源文件   这个类提供软件国际化的捷径.通过此类,可以使您所编写的程序可以:          轻松地本地化或翻译成不同的 ...

  5. java之StringBuffer类详解

    StringBuffer 线程安全的可变字符序列. StringBuffer源码分析(JDK1.6): public final class StringBuffer extends Abstract ...

  6. java.util.ResourceBundle使用详解

    java.util.ResourceBundle使用详解   一.认识国际化资源文件   这个类提供软件国际化的捷径.通过此类,可以使您所编写的程序可以:          轻松地本地化或翻译成不同的 ...

  7. java之AbstractStringBuilder类详解

    目录 AbstractStringBuilder类 字段 构造器 方法   public abstract String toString() 扩充容量 void  expandCapacity(in ...

  8. java之StringBuilder类详解

    StringBuilder 非线程安全的可变字符序列 .该类被设计用作StringBuffer的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍).如果可能,建议优先采用该类,因为在 ...

  9. java.util.ResourceBundle使用详解(转)

    java.util.ResourceBundle使用详解   一.认识国际化资源文件   这个类提供软件国际化的捷径.通过此类,可以使您所编写的程序可以:          轻松地本地化或翻译成不同的 ...

随机推荐

  1. openvswitch 驱动卸载失败(Module openvswitch is in use)

    现象: [root@ostack1 ~]# modprobe -r openvswitchmodprobe: FATAL: Module openvswitch is in use. 解决: [roo ...

  2. kolla-ansible 源码下载

    下载地址: https://pypi.org/project/kolla-ansible/ ansible下载: https://releases.ansible.com/ansible/rpm/re ...

  3. 689. Maximum Sum of 3 Non-Overlapping Subarrays

    In a given array nums of positive integers, find three non-overlapping subarrays with maximum sum. E ...

  4. 639. Decode Ways II

    A message containing letters from A-Z is being encoded to numbers using the following mapping way: ' ...

  5. 这里给大家介绍一下通过 Wi-Fi 连接“慷慨捐赠”你的身份信息的七种方法.

    这里给大家介绍一下通过 Wi-Fi 连接“慷慨捐赠”你的身份信息的七种方法和反制措施. 本文作者:黑子小盆友 1.利用免费热点 它们似乎无处不在,而且它们的数量会在接下来四年里增加三倍.但是它们当中很 ...

  6. ACTIVEMQ监控项目admin队列详情中文乱码

    一.使用ACTIVEMQ队列,传入ObjectMessage时,监控项目admin无法解析消息信息,需要将消息javabean打成jar放入lib文件夹中,重启ACTIVEMQ,注意javabean要 ...

  7. RabbitMQ - Start Up

    开始之前 rabbitmq是一个被广泛使用的消息队列,它是由erlang编写的,根据AMQP协议设计实现的. AMQP的主要特征是面向消息.队列.路由(包括点对点和发布/订阅).可靠性.安全. Rab ...

  8. 五:MyBatis学习总结(五)——实现关联表查询

    一.一对一关联 1.1.提出需求 根据班级id查询班级信息(带老师的信息) 1.2.创建表和数据 创建一张教师表和班级表,这里我们假设一个老师只负责教一个班,那么老师和班级之间的关系就是一种一对一的关 ...

  9. 使用git工具将本地电脑上的代码上传至GitHub

    本文教你如果使用git工具将本地电脑上的代码上传至GitHub 1.安装git工具 安装git链接 2.使用git工具上传自己的代码到GitHub中 安装完git工具之后,我们会得到两个命令行工具,一 ...

  10. 【qboi冲刺NOIP2017复赛试题4】 全套题目+题解+程序

    作为一个好人(验题人),我给大家奉上下这套题的题解,并且预祝大家这套题能够AK: T1题面:Alice现在有n根木棍,他们长度为1,2,3....n,Bob想把某一些木棍去掉,使得Alice剩下的木棍 ...