饿汉式
package com.li.single;
public class Single1 {
// 私有实例,类初始化就加载
private static Single1 instance = new Single1();
// 私有构造方法
private Single1() {}
// 公共的、静态的获取实例方法
public static Single1 getInstance() {
return instance;
}
public static void main(String[] args) {
Single instance = Single.getInstance();
Single instance1 = Single.getInstance();
System.out.println(instance);
System.out.println(instance1);
}
}
饿汉式:
1. 类加载时就初始化,浪费内存,不能延迟加载;
2. 基于 classloader 机制避免了多线程的同步问题,线程安全;
3. 没有加锁,调用效率高。
懒汉式
public class SingletonDemoInLazy {
// 私有实例,初始化的时候不加载(延迟加载)
private static SingletonDemoInLazy instance;
// 私有构造
private SingletonDemoInLazy() {}
// 公共获取实例方法(线程不安全)
public static SingletonDemoInLazy getInstance() {
if(instance == null ) { // 使用的时候加载
instance = new SingletonDemoInLazy();
}
return instance;
}
}
上面这种写法,是线程不安全的,但是可以做到延迟加载
public class SingletonDemoInLazy {
// 私有实例,初始化的时候不加载(延迟加载)
private static SingletonDemoInLazy instance;
// 私有构造
private SingletonDemoInLazy() {}
// 公共获取实例方法(线程安全,调用效率低)
public synchronized static SingletonDemoInLazy getInstance() {
if(instance == null ) {
instance = new SingletonDemoInLazy();
}
return instance;
}
}
上面代码中,通过关键字synchronized声明公共的获取实例的方法getInstance(),可以确保线程安全,能做到延迟加载,但是效率不高
double-checked locking(双重检查锁)
package com.li.single;
import lombok.SneakyThrows;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
public class Single {
// 私有实例,volatile关键字,禁止指令重排。
private volatile static Single instance;
private static boolean flag = false;
// 私有构造 这里在双重锁的基础上再次加锁,为了避免反射破坏,但是使用反射可以获取字段名并更改值,所以还是可以使用反射破坏单例
private Single() {
synchronized (Single.class) {
if (flag == false) {
flag = true;
} else {
throw new RuntimeException("使用反射破坏单例");
}
}
}
// 公共获取实例方法(线程安全)
public static Single getInstance() {
if (instance == null) { // 一重检查
synchronized (Single.class) {
if (instance == null) { // 二重检查
instance = new Single();
}
}
}
return instance;
}
@SneakyThrows
public static void main(String[] args) {
Single instance = Single.getInstance();
System.out.println(instance);
//使用反射的空参构造获取对象
Constructor<Single> declaredConstructor = Single.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
//使用反射获取字段
Field flag = Single.class.getDeclaredField("flag");
flag.setAccessible(true);
//更改字段的值
flag.set(declaredConstructor,false);
Single single = declaredConstructor.newInstance();
System.out.println(single);
}
}
在加锁之前判断是否为空,可以确保 instance 不为空的情况下,不用加锁,可以直接返回。
加锁之后,还需要判断 instance 是否为空,是为了防止在多线程并发的情况下,会实例化多个对象。例如:线程 a 和线程 b 同时调用 getInstance 方法,假如同时判断 instance 都为空,这时会同时进行抢锁。假如线程 a 先抢到锁,开始执行 synchronized 关键字包含的代码,此时线程 b 处于等待状态。线程 a 创建完新实例了,释放锁了,此时线程 b 拿到锁,进入 synchronized 关键字包含的代码,如果没有再判断一次 instance 是否为空,则可能会重复创建实例
加volatile关键字原因
这是因为编译器在编译时会进行指令重排,而 volatile 可以禁止指令重排。
对象的创建大概有这么三个步骤(从指令层面来看啊,从JVM来讲具体还有很多步骤):
- 分配内存空间
- 执行构造方法,初始化对象
- 把这个对象指向这个空间
正常是按 123 的顺序执行,但由于指令重排的存在,可能会存在 A 线程按照132 执行,当执行到 3 时,线程 B 来取对象,就会得到空值。因此必须给单例对象加上 volatile 关键字。
双重检查锁:
1. 双重判断,延迟加载;
2. 线程安全;
3. JDK 版本要求1.5起
也叫DCL模式
静态内部类
public class SingletonDemoInStaticInnerClass {
// 静态内部类
private static class InnerClass{
// 初始化实例
private final static SingletonDemoInStaticInnerClass INSTANCE = new SingletonDemoInStaticInnerClass();
}
// 私有构造
private SingletonDemoInStaticInnerClass() {}
// 公关获取实例方法(线程安全,延迟加载)
public static SingletonDemoInStaticInnerClass getInstance() {
return InnerClass.INSTANCE;
}
}
静态内部类:
1. 利用了classloader机制来保证初始化 instance 时只有一个线程,线程安全;
2. 只有通过显式调用 getInstance 方法时,才会显式装载静态内部类,从而实例化instance,延迟加载
枚举
public enum SingletonEnum {
// 枚举元素本身就是单例
INSTANCE;
// 其他要执行的方法
public void sayHello() {
System.out.println("你好");
}
......
}
枚举:这是实现单例模式的最佳方法。它更简洁,不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。但是不是延迟加载的,是不可以使用反射破坏单例机制的