前言
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种模式涉及单一的类,需确保代码中最多只有一个该类对象被创建。
原文地址:https://xuedongyun.cn/post/25714/
懒汉式
饿汉式,在一开始就创建对象
方法一:静态成员变量
1 2 3 4 5 6 7 8 9 10 11 12
| public class Singleton { private Singleton() {} private static Singleton instance = new Singleton(); public static Singleton getInstance() { return instance; } }
|
方法二:静态代码块
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class Singleton { private Singleton() {}
private static Singleton instance; static { instance = new Singleton(); }
public static Singleton getInstance() { return instance; } }
|
饿汉式
方式一:线程不安全
第一次使用时才创建对象
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class Singleton { private Singleton() {}
private static Singleton instance;
public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
|
方式二:线程安全
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class Singleton { private Singleton() {}
private static Singleton instance; public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
|
方式三:双重检查锁
因为绝大多数操作都是读操作,给方法加锁影响性能,可以改为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class Singleton { private Singleton() {}
private static Singleton instance; public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }
|
上述代码看起来很完美,但实际上可能会有空指针问题,原因是JVM在实例化对象的时候会进行指令重排
在java中,创建对象实际上不是不是原子操作,而是有三步:
1 2 3 4 5 6 7 8
| memory = allocate();
ctorInstance(memory);
instance = memory;
|
指令重排后,就可能变成:
1 2 3 4 5 6 7 8
| memory = allocate();
instance = memory;
ctorInstance(memory);
|
上述懒汉式代码中,很有可能:某个线程获取了非空instance,但是此时还没来得及初始化。我们可以使用volatile关键词来解决这个问题
volatile 关键字有如下特征:
- 可见性:当一个线程修改了这个变量的值,volatile保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。但普通变量做不到这点,普通变量的值在线程间传递均需要通过主内存
- 禁止指令重排序优化:被volatile 修饰的变量,不会被JVM指令重排
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class Singleton {
private Singleton() {} private static volatile Singleton instance;
public static Singleton getInstance() { if(instance == null) { synchronized (Singleton.class) { if(instance == null) { instance = new Singleton(); } } } return instance; } }
|
方式四:静态内部类
首次使用时,静态内部类才会被加载,进而初始化INSTANCE。这是开源项目中一种较为常见的单例模式
1 2 3 4 5 6 7 8 9 10 11
| public class Singleton { private Singleton() {}
private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); }
public static Singleton getInstance() { return SingletonHolder.INSTANCE; } }
|
方法五:枚举方式
这是一种极力推荐的单例实现模式,因为枚举类是线程安全的,且只会装载一次。枚举类无法使用反射创建,因此是唯一不会被破坏的单例模式。
1 2 3 4 5 6
| public enum Singleton { INSTANCE; private int value; private String name; }
|
存在的问题
序列化和反射能破坏单例模式(枚举方法除外)
序列化破坏单例模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| Singleton instance = Singleton.getInstance();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:/test.txt")); oos.writeObject(instance);
ObjectInputStream ois1 = new ObjectInputStream(new FileInputStream("C:/test.txt")); Singleton singleton1 = (Singleton) ois1.readObject();
ObjectInputStream ois2 = new ObjectInputStream(new FileInputStream("C:/test.txt")); Singleton singleton2 = (Singleton) ois2.readObject();
|
反射破坏单例模式
1 2 3 4 5 6
| Class<Singleton> clazz = Singleton.class; Constructor<Singleton> constructor = clazz.getDeclaredConstructor(); constructor.setAccessible(true);
Singleton singleton1 = constructor.newInstance(); Singleton singleton2 = constructor.newInstance();
|
解决方法
解决序列化破坏
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class Singleton implements Serializable { private Singleton() {}
private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); }
public static Singleton getInstance() { return SingletonHolder.INSTANCE; } private Object readResolve() { return SingletonHolder.INSTANCE; } }
|
原理:可以看看ObjectInputStream.readObject
的源码:
readObject
->readObject0
->readOrdinaryObject
->如果有readResolve()
方法,在反序列化的时候会调用该方法返回对象
1 2 3 4
| if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()) { Object rep = desc.invokeReadResolve(obj); }
|
解决反射破坏
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class Singleton {
private Singleton() { if(instance != null) { throw new RuntimeException(); } } }
|
JDK源码-Runtime类
Runtime类使用饿汉式(静态变量)实现
1 2 3 4 5 6 7
| public class Runtime { private static final Runtime currentRuntime = new Runtime();
public static Runtime getRuntime() { return currentRuntime; } }
|
Runtime类的使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| Runtime runtime = Runtime.getRuntime();
long totalMemory = runtime.totalMemory(); System.out.println("totalMemory = " + totalMemory);
long maxMemory = runtime.maxMemory(); System.out.println("maxMemory = " + maxMemory);
Process process = runtime.exec("ipconfig");
InputStream is = process.getInputStream(); byte[] bytes = new byte[1024 * 1024 * 100]; int i = is.read(bytes); System.out.println(new String(bytes, 0, i, "gbk"));
|