定义
指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点,属于创建型设计模式。
应用场景
- 需要频繁创建的一些类,使用单例可以降低系统的内存压力,减少GC;
- 某些类创建实例时占用资源比较多,或实例化耗时比较长,且经常使用;
- 频繁访问数据库或文件的对象;
- 对于一些控制硬件级别的操作,或从系统上讲应当是单一控制逻辑的操作,如果有多个实例,则系统会完全乱掉。
饿汉模式
在类加载的时候立即初始化,并且创建单例对象
package com.java.design.singleton;
/**
* 饿汉式单例:
* 线程安全、调用率高,但不能延迟加载
* @author liyongfu
*/
public class HungrySingleton {
private HungrySingleton() {}
private static HungrySingleton singleton = new HungrySingleton();
public static HungrySingleton getInstance() {
return singleton;
}
/**
* 返回是同一实例
*
* @param args
*/
public static void main(String[] args) throws InterruptedException {
int threadNum = 150;
for (int i = 100; i < threadNum; i++) {
int finalI = i;
new Thread(() -> {
HungrySingleton instance = HungrySingleton.getInstance();
System.out.println(finalI +" : " + instance);
}).start();
}
}
}
效果
100 : com.java.design.singleton.HungrySingleton@e0ab2af
103 : com.java.design.singleton.HungrySingleton@e0ab2af
101 : com.java.design.singleton.HungrySingleton@e0ab2af
102 : com.java.design.singleton.HungrySingleton@e0ab2af
104 : com.java.design.singleton.HungrySingleton@e0ab2af
105 : com.java.design.singleton.HungrySingleton@e0ab2af
106 : com.java.design.singleton.HungrySingleton@e0ab2af
107 : com.java.design.singleton.HungrySingleton@e0ab2af
108 : com.java.design.singleton.HungrySingleton@e0ab2af
109 : com.java.design.singleton.HungrySingleton@e0ab2af
110 : com.java.design.singleton.HungrySingleton@e0ab2af
111 : com.java.design.singleton.HungrySingleton@e0ab2af
112 : com.java.design.singleton.HungrySingleton@e0ab2af
113 : com.java.design.singleton.HungrySingleton@e0ab2af
114 : com.java.design.singleton.HungrySingleton@e0ab2af
115 : com.java.design.singleton.HungrySingleton@e0ab2af
116 : com.java.design.singleton.HungrySingleton@e0ab2af
117 : com.java.design.singleton.HungrySingleton@e0ab2af
118 : com.java.design.singleton.HungrySingleton@e0ab2af
119 : com.java.design.singleton.HungrySingleton@e0ab2af
120 : com.java.design.singleton.HungrySingleton@e0ab2af
121 : com.java.design.singleton.HungrySingleton@e0ab2af
122 : com.java.design.singleton.HungrySingleton@e0ab2af
123 : com.java.design.singleton.HungrySingleton@e0ab2af
124 : com.java.design.singleton.HungrySingleton@e0ab2af
125 : com.java.design.singleton.HungrySingleton@e0ab2af
126 : com.java.design.singleton.HungrySingleton@e0ab2af
127 : com.java.design.singleton.HungrySingleton@e0ab2af
128 : com.java.design.singleton.HungrySingleton@e0ab2af
129 : com.java.design.singleton.HungrySingleton@e0ab2af
130 : com.java.design.singleton.HungrySingleton@e0ab2af
131 : com.java.design.singleton.HungrySingleton@e0ab2af
132 : com.java.design.singleton.HungrySingleton@e0ab2af
133 : com.java.design.singleton.HungrySingleton@e0ab2af
134 : com.java.design.singleton.HungrySingleton@e0ab2af
135 : com.java.design.singleton.HungrySingleton@e0ab2af
136 : com.java.design.singleton.HungrySingleton@e0ab2af
137 : com.java.design.singleton.HungrySingleton@e0ab2af
138 : com.java.design.singleton.HungrySingleton@e0ab2af
139 : com.java.design.singleton.HungrySingleton@e0ab2af
140 : com.java.design.singleton.HungrySingleton@e0ab2af
141 : com.java.design.singleton.HungrySingleton@e0ab2af
142 : com.java.design.singleton.HungrySingleton@e0ab2af
143 : com.java.design.singleton.HungrySingleton@e0ab2af
144 : com.java.design.singleton.HungrySingleton@e0ab2af
145 : com.java.design.singleton.HungrySingleton@e0ab2af
147 : com.java.design.singleton.HungrySingleton@e0ab2af
148 : com.java.design.singleton.HungrySingleton@e0ab2af
146 : com.java.design.singleton.HungrySingleton@e0ab2af
149 : com.java.design.singleton.HungrySingleton@e0ab2af
优点
在类加载的时候立即初始化,并且创建单例对象,绝对线程安全;
缺点
在类加载的时候立即初始化,如果系统中有大批量的单例对象存在,而且单例对象的数量也不确定,则在系统初始化时会造成大量的内存浪费,从而导致系统内存不可控。
懒汉模式
特点:单例对象在被使用时才会初始化(延迟加载);
package com.java.design.singleton;
/**
* 懒汉式单例 + 同步
*
* @author liyongfu
*/
public class LazySingleton {
/**
* 持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载
*/
private static LazySingleton instance = null;
/**
* 私有构造方法,防止被实例化
*/
private LazySingleton() {}
/**
* 静态工程方法,创建实例
* 添加同步锁synchronized,缺点是影响性能
*/
public static synchronized LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
/**
* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致
*/
public Object readResolve() {
return instance;
}
public static void main(String[] args) throws Exception {
int threadNum = 250;
for (int i = 200; i < threadNum; i++) {
int finalI = i;
new Thread(() -> {
LazySingleton instance = LazySingleton.getInstance();
System.out.println(finalI + " : " + instance);
}).start();
}
}
}
效果
200 : com.java.design.singleton.LazySingleton@148f5a24
203 : com.java.design.singleton.LazySingleton@148f5a24
201 : com.java.design.singleton.LazySingleton@148f5a24
202 : com.java.design.singleton.LazySingleton@148f5a24
204 : com.java.design.singleton.LazySingleton@148f5a24
205 : com.java.design.singleton.LazySingleton@148f5a24
207 : com.java.design.singleton.LazySingleton@148f5a24
206 : com.java.design.singleton.LazySingleton@148f5a24
209 : com.java.design.singleton.LazySingleton@148f5a24
208 : com.java.design.singleton.LazySingleton@148f5a24
210 : com.java.design.singleton.LazySingleton@148f5a24
211 : com.java.design.singleton.LazySingleton@148f5a24
212 : com.java.design.singleton.LazySingleton@148f5a24
213 : com.java.design.singleton.LazySingleton@148f5a24
214 : com.java.design.singleton.LazySingleton@148f5a24
215 : com.java.design.singleton.LazySingleton@148f5a24
216 : com.java.design.singleton.LazySingleton@148f5a24
217 : com.java.design.singleton.LazySingleton@148f5a24
218 : com.java.design.singleton.LazySingleton@148f5a24
219 : com.java.design.singleton.LazySingleton@148f5a24
221 : com.java.design.singleton.LazySingleton@148f5a24
220 : com.java.design.singleton.LazySingleton@148f5a24
222 : com.java.design.singleton.LazySingleton@148f5a24
223 : com.java.design.singleton.LazySingleton@148f5a24
225 : com.java.design.singleton.LazySingleton@148f5a24
224 : com.java.design.singleton.LazySingleton@148f5a24
227 : com.java.design.singleton.LazySingleton@148f5a24
226 : com.java.design.singleton.LazySingleton@148f5a24
228 : com.java.design.singleton.LazySingleton@148f5a24
229 : com.java.design.singleton.LazySingleton@148f5a24
230 : com.java.design.singleton.LazySingleton@148f5a24
231 : com.java.design.singleton.LazySingleton@148f5a24
232 : com.java.design.singleton.LazySingleton@148f5a24
234 : com.java.design.singleton.LazySingleton@148f5a24
236 : com.java.design.singleton.LazySingleton@148f5a24
233 : com.java.design.singleton.LazySingleton@148f5a24
238 : com.java.design.singleton.LazySingleton@148f5a24
237 : com.java.design.singleton.LazySingleton@148f5a24
235 : com.java.design.singleton.LazySingleton@148f5a24
240 : com.java.design.singleton.LazySingleton@148f5a24
239 : com.java.design.singleton.LazySingleton@148f5a24
241 : com.java.design.singleton.LazySingleton@148f5a24
242 : com.java.design.singleton.LazySingleton@148f5a24
243 : com.java.design.singleton.LazySingleton@148f5a24
245 : com.java.design.singleton.LazySingleton@148f5a24
246 : com.java.design.singleton.LazySingleton@148f5a24
244 : com.java.design.singleton.LazySingleton@148f5a24
247 : com.java.design.singleton.LazySingleton@148f5a24
248 : com.java.design.singleton.LazySingleton@148f5a24
249 : com.java.design.singleton.LazySingleton@148f5a24
双重检查锁(DCL)
public class DoubleCheckLockSingleton {
private static volatile DoubleCheckLockSingleton instance;
private DoubleCheckLockSingleton() {}
public static DoubleCheckLockSingleton getInstance() {
/**检查是否要引起阻塞*/
if (instance == null) {
synchronized (DoubleCheckLockSingleton.class) {
/**检查是否要重新创建实例*/
if (instance == null) {
instance = new DoubleCheckLockSingleton();
/**指令重排序的问题*/
}
}
}
return instance;
}
}
思考
volatile关键字在此处起了什么作用?
为何要执行两次instance == null判断?
当第一个线程调用getInstance()方法时,第二个线程也可以调用。当第一个线程执行到synchronized时会上锁,第二个线程会变成MONITOR状态,出现阻塞。此时,阻塞并不是基于整个DoubleCheckLockSingleton类的阻塞,而是在getInstance()方法内部的阻塞,只要逻辑不是很复杂,对于调用者而言是感觉不到的。
优点
解决了线程安全和性能问题。
静态内部类
public class StaticInnerHolderSingleton {
private static class SingletonHolder {
private static final StaticInnerHolderSingleton INSTANCE = new StaticInnerHolderSingleton();
}
private StaticInnerHolderSingleton() {}
public static StaticInnerHolderSingleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
思考:
这种方式是通过什么机制保证线程安全性与延迟加载的?(注意,这是Java单例的两大要点,必须保证)
枚举
public enum EnumSingleton {
INSTANCE;
}
思考
Java枚举的本质是?
这种方式又是通过什么机制保证线程安全性与延迟加载的?
如何破坏一个单例
反射攻击
public class SingletonAttack {
public static void main(String[] args) throws Exception {
reflectionAttack();
}
public static void reflectionAttack() throws Exception {
Constructor constructor = DoubleCheckLockSingleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
DoubleCheckLockSingleton s1 = (DoubleCheckLockSingleton)constructor.newInstance();
DoubleCheckLockSingleton s2 = (DoubleCheckLockSingleton)constructor.newInstance();
s1.tellEveryone();
s2.tellEveryone();
System.out.println(s1 == s2);
}
}
This is a DoubleCheckLockSingleton 1368884364
This is a DoubleCheckLockSingleton 401625763
false
这种方法非常简单暴力,通过反射侵入单例类的私有构造方法并强制执行,使之产生多个不同的实例,这样单例就被破坏了。要防御反射攻击,只能在单例构造方法中检测instance是否为null,如果已不为null,就抛出异常。显然双重检查锁实现无法做这种检查,静态内部类实现则是可以的。
注意,不能在单例类中添加类初始化的标记位或计数值(比如boolean flag、int count)来防御此类攻击,因为通过反射仍然可以随意修改它们的值。
序列化攻击
这种攻击方式只对实现了Serializable接口的单例有效,但偏偏有些单例就是必须序列化的。现在假设DoubleCheckLockSingleton类已经实现了该接口
public class SingletonAttack {
public static void main(String[] args) throws Exception {
serializationAttack();
}
public static void serializationAttack() throws Exception {
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("serFile"));
DoubleCheckLockSingleton s1 = DoubleCheckLockSingleton.getInstance();
outputStream.writeObject(s1);
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(new File("serFile")));
DoubleCheckLockSingleton s2 = (DoubleCheckLockSingleton)inputStream.readObject();
s1.tellEveryone();
s2.tellEveryone();
System.out.println(s1 == s2);
}
}
This is a DoubleCheckLockSingleton 777874839
This is a DoubleCheckLockSingleton 254413710
false