单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例
场景:重量级的对象,不需要多个实例,如线程池,数据库连接池
实现的关键点:私有化构造方法、提实现供公有的访问方式
常见实现方式:懒汉式(及其优化)、饿汉式、静态内部类、枚举
- 懒汉式
延迟加载, 只有在真正使用的时候,才开始实例化。
优缺点:最简单的懒汉式有线程安全的问题, 这里直接给出double-check模式版本,虽然优化了线程安全的问题,但是逻辑稍显复杂了。
/**
*/
public class LazySingletonTest {
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
LazySingleton instance = LazySingleton.getInstance();
System.out.println(instance);
});
Thread thread2 = new Thread(() -> {
LazySingleton instance = LazySingleton.getInstance();
System.out.println(instance);
});
thread1.start();
thread2.start();
}
}
class LazySingleton {
// volatile 禁止指令重排序
private volatile static LazySingleton instance;
private LazySingleton() {}
public static LazySingleton getInstance() {
if (instance == null) {
synchronized (LazySingleton.class) {
if (instance == null) {
instance = new LazySingleton();
}
}
}
return instance;
}
}
关键点分析
synchronized:保证线程安全;
双重非空判断:
并不是每次进入getInstance方法都需要同步,首次非空判断后才加锁,缩小了加锁代码块的范围;
第二次非空判断,防止代码被初始化两次;
volatile :编译器(JIT),CPU 有可能对指令进行重排序,导致使用到尚未初始化
的实例,导致非空判断失效,可以通过添加volatile 关键字进行修饰。被volatile修饰的变量的值,将不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确的处理该变量
- 饿汉模式
饿汉式其实是一种比较形象的称谓。既然饿,那么在创建对象实例的时候就比较着急,饿了嘛,于是在装载类的时候就创建对象实例。
典型的空间换时间,当类装载的时候就会创建类的实例,不管你用不用,先创建出来,然后每次调用的时候,就不需要再判断,节省了运行时间。
优缺点:饿汉式天生线程安全(jvm类加载机制,后面具体分析),但是因为在装在的时候就直接创建了对象,大量创建使用的时候会导致内存的开销。
/**
*/
public class HungrySingletonTest {
public static void main(String[] args) {
HungrySingleton instance=HungrySingleton.getInstance();
HungrySingleton instance1=HungrySingleton.getInstance();
System.out.println(instance==instance1);
}
}
class HungrySingleton{
private static HungrySingleton instance = new HungrySingleton();
private HungrySingleton(){
}
public static HungrySingleton getInstance() {
return instance;
}
}
饿汉式本质上就是借助于jvm类加载机制,保证实例的唯一性(初始化过程只会执行一次)及线程安全(JVM以同步的形式来完成类加载的整个过程,这种思路可以在平时编码的过程中多借鉴)。
类加载过程:
1,加载二进制数据到内存中, 生成对应的Class数据结构,
2,连接: a. 验证, b.准备(给类的静态成员变量赋默认值),c.解析
3,初始化: 给类的静态变量赋初值
- 静态内部类
1).本质上是利用类的加载机制来保证线程安全
2).只有在实际使用的时候,才会触发类的初始化,所以也是懒加载的一
种形式。
package com.tuling.designpattern.singleton.v3;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
public class InnerClassSingletonTest {
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
InnerClassSingleton instance1 = InnerClassSingleton.getInstance();
System.out.println(instance1);
});
Thread thread2 =new Thread(()->{
InnerClassSingleton instance1=InnerClassSingleton.getInstance();
System.out.println(instance1);
});
thread1.start();
thread2.start();
}
}
class InnerClassSingleton implements Serializable {
// private static final long serialVersionUID = 6922639953390195232L;
// private static final long serialVersionUID = 42L;
public static String name="yyy";
public static String name1="yyy";
public static String name2="yyy";
static {
System.out.println( " InnerClassSingleton " ); // 1
}
private InnerClassSingleton() {
if (SingletonHolder.instance != null) {
throw new RuntimeException( " 不允许多个实例。" );
}
}
public static InnerClassSingleton getInstance() {
return SingletonHolder.instance;
}
Object readResolve() throws ObjectStreamException {
return SingletonHolder.instance;
}
private static class SingletonHolder {
private static InnerClassSingleton instance=new InnerClassSingleton();
static {
System.out.println( " SingletonHolder " );// 2
}
}
}
类在装载并被初始化的时候,会初始化它的静态域,从而创建Singleton的实例,由于是静态的域,因此只会在虚拟机装载类的时候初始化一次,并由虚拟机来保证它的线程安全性。
这个模式的优势在于,getInstance方法并没有被同步,并且只是执行一个域的访问,因此延迟初始化并没有增加任何访问成本
- 枚举
参考文献