设计模式是一套被反复使用、多人知晓、经过分类编目、代码设计经验的总结。是人们实践的产物。
单例模式能够保证整个应用中有且只有一个实例,即保证一个类在内存中的对象的唯一性。单例模式使用一个私有构造函数、一个私有静态变量、一个公有静态函数来实现。
五种实现方式
饿汉式
import java.io.Serializable;
/**
* @author Margaret
* @create 2022-01-13 16:02
*/
//饿汉式
public class Singleton1 implements Serializable{
private Singleton1() {
System.out.println("这是私有构造函数");
}
public static Singleton1 instance = new Singleton1();
public static Singleton1 getInstance() {
return instance;
}
public static void otherMethod() {
System.out.println("其他方法");
}
}
写一个测试方法:
public class SingletonTest {
public static void main(String[] args) {
Singleton1.otherMethod();
System.out.println("========================================");
System.out.println(Singleton0.getInstance());
System.out.println(Singleton0.getInstance());
}
}
输出结果:
但是,可以通过反射和反序列化以及unsafe()来破坏单例,如:
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
/**
* @author Margaret
* @create 2022-01-13 16:03
*/
public class SingletonTest {
public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException, IOException, ClassNotFoundException {
Singleton1.otherMethod();
System.out.println("==========================");
System.out.println(Singleton1.getInstance());
System.out.println(Singleton1.getInstance());
//反射破坏单例
reflection(Singleton1.class);
//反序列破坏单例
//serializable(Singleton1.getInstance());
//Unsafe破坏单例
//unsafe(Singleton1.class);
}
private static void serializable(Singleton1 instance) throws IOException, ClassNotFoundException {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(instance);
ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
System.out.println("反序列化创建实例:" + objectInputStream.readObject());
}
private static void reflection(Class<?> clazz) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Constructor<?> constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
System.out.println("反射创建实例:" + constructor.newInstance());
}
private static void unsafe(Class<?> clazz) {
/*Object o = UnsafeUtils.getUnsafe().allocateInstance(clazz);
System.out.println("Unsafe创建实例:" + o);*/
}
}
输出结果:
类似的,反序列化也会破坏单例,为避免反射和反序列化破坏单例,可以作如下修改:
public class Singleton1 implements Serializable {
private Singleton1() {
if (INSTANCE != null) {//防止反射破坏单例!
throw new RuntimeException("单例对象不能重复创建");
}
System.out.println("private Singleton1");
}
private static final Singleton1 INSTANCE = new Singleton1();//给静态变量赋值的代码会放到静态代码块中,由JVM保证线程安全,我们不需要考虑线程安全问题。
public static Singleton1 getInstance() {
return INSTANCE;
}//直接实例化
public static void otherMethod() {
System.out.println("otherMethod");
}
//防止反序列化破坏单例
public Object readResolve() {
return INSTANCE;
}
}
构造方法抛出异常是防止反射破坏单例。
readResolve()
是防止反序列化破坏单例。
unsafe()
破坏单例还没有办法避免。
懒汉式
import java.io.Serializable;
/**
* @author Margaret
* @create 2022-01-13 17:15
*/
//懒汉式
public class Singleton3 implements Serializable {
private Singleton3() {
System.out.println("private Singleton3()");
}
private static Singleton3 INSTANCE = null;
//多线程下使用会不安全,在静态方法上加一个synchronized相当于给这个方法加了一把锁,可以保证方法的原子操作
public static synchronized Singleton3 getInstance() {
if (INSTANCE != null) {
INSTANCE = new Singleton3();
}
return INSTANCE;
}
public static void otherMethod() {
System.out.println("otherMethod()");
}
}
其实只有首次创建单例对象时需要同步,但实际上每次调用都会同步,因此有了DCL改进。
DCL(双重检查锁)
//DCL 双检锁
public class Singleton4 implements Serializable {
private Singleton4() {
System.out.println("private Singleton4()");
}
private static volatile Singleton4 INSTANCE = null;//volatile修饰共享变量可以阻止指令重排序,保证指令的有序性
public static void getInstance() {
if (INSTANCE != null) {
synchronized (Singleton4.class) {//synchronized前后都加上if判断
if (INSTANCE != null) {
INSTANCE = new Singleton4();
}
}
}
}
}
之所以必须加 volatile的原因:
INSTANCE = new Singleton4()
不是原子的,分成 3 步:创建对象、调用构造、给静态变量赋值,其中后两步可能被指令重排序优化,变成先赋值、再调用构造- 如果线程1 先执行了赋值,线程2 执行到第一个
INSTANCE == null
时发现 INSTANCE 已经不为 null,此时就会返回一个未完全构造的对象
枚举饿汉式
//枚举
public enum Singleton2 {
INSTANCE;
private Singleton2() {
System.out.println("private Singleton2()");
}
@Override
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
public static Singleton2 getInstance() {
return INSTANCE;
}
public static void otherMethod() {
System.out.println("otherMethod()");
}
}
枚举饿汉式能天然防止反射、反序列化破坏单例。
内部类懒汉式
import java.io.Serializable;
/**
* @author Margaret
* @create 2022-01-13 22:08
*/
public class Singleton5 implements Serializable {
private Singleton5() {
System.out.println("private Singleton5()");
}
private static class Holder {
static Singleton5 INSTANCE = new Singleton5();
}
public static Singleton5 getInstance() {
return Holder.INSTANCE;
}
public static void otherMethod() {
System.out.println("otherMethod()");
}
}
内部类懒汉式避免了双检锁的缺点。