模式定义:
保证一个类只有一个实例,并且提供一个全局访问点
场景:
重量级的对象,不需要多个实例,如线程池,数据库连接池。
延迟加载, 只有在真正使用的时候,才开始实例化。
基本实现:
public class LazySingletonTest {
public static void main(String[] args) {
LazySingleton instance1 = LazySingleton.getInstance();
LazySingleton instance2 = LazySingleton.getInstance();
System.out.println(instance1==instance2);
}
}
class LazySingleton{
private static LazySingleton instance;
private LazySingleton(){
}
public static LazySingleton getInstance(){
if(instance==null){
instance = new LazySingleton();
}
return instance;
}
}
单线程情况下没问题,但是多线程下就存在线程安全问题
public class LazySingletonTest {
public static void main(String[] args) {
new Thread(()->{
LazySingleton instance = LazySingleton.getInstance();
System.out.println(instance);
}).start();
new Thread(()->{
LazySingleton instance = LazySingleton.getInstance();
System.out.println(instance);
}).start();
}
}
class LazySingleton{
private static LazySingleton instance;
private LazySingleton(){
}
public static LazySingleton getInstance(){
if(instance==null){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new LazySingleton();
}
return instance;
}
}
第一步优化,加锁并双重检查
public static LazySingleton getInstance(){
if(instance==null){
synchronized (LazySingleton.class){
if(instance==null){
instance = new LazySingleton();
}
}
}
return instance;
}
第二部优化,防治指令重排,造成并发情况下出现空指针,加入volatile关键字。
OK,保证线程安全的懒汉模式就是:
class LazySingleton{
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;
}
}
2.饿汉模式
类加载的 初始化阶段就完成了 实例的初始化 。本质上就是借助于jvm类加载机制,保证实例的唯一性(初始化过程只会执行一次)及线程安全(JVM以同步的形式来完成类加载的整个过程)。
实现:
class HungrySingleton{
private static HungrySingleton instance = new HungrySingleton();
private HungrySingleton(){
}
public static HungrySingleton getInstance(){
return instance;
}
}
3.静态内部类
- 本质上是利用类的加载机制来保证线程安全
- 只有在实际使用的时候,才会触发类的初始化,所以也是懒加载的一种形式。
class InnerClazzSingleton{
private static class InnerClassHolder{
private static InnerClazzSingleton instance = new InnerClazzSingleton();
}
private InnerClazzSingleton(){
}
public static InnerClazzSingleton getInstance(){
return InnerClassHolder.instance;
}
}
4.反射攻击
对于上述的懒汉、恶汉、静态内部类的单例模式,均可以通过反射破解,以静态内部类为例:
public class InnerClazzSingletonTest {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Constructor constructor = InnerClazzSingleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
InnerClazzSingleton innerClazzSingleton = constructor.newInstance();
InnerClazzSingleton instance = InnerClazzSingleton.getInstance();
System.out.println(innerClazzSingleton==instance);
}
}
class InnerClazzSingleton{
private static class InnerClassHolder{
private static InnerClazzSingleton instance = new InnerClazzSingleton();
}
private InnerClazzSingleton(){
}
public static InnerClazzSingleton getInstance(){
return InnerClassHolder.instance;
}
}
- 对于恶汉模式和静态内部类可以用以下方式防止反射破解,但是懒汉模式不能防止反射破解
class InnerClazzSingleton{
private static class InnerClassHolder{
private static InnerClazzSingleton instance = new InnerClazzSingleton();
}
private InnerClazzSingleton(){
if(InnerClassHolder.instance!=null){
throw new RuntimeException("单例不允许多个实例");
}
}
public static InnerClazzSingleton getInstance(){
return InnerClassHolder.instance;
}
}
5.枚举类型
天然不支持反射创建对应的实例,且有自己的反序列化机制。
利用类加载机制保证线程安全。
public enum EnumSingleton {
INSTANCE;
}
class EnumSingletonTest{
public static void main(String[] args) {
EnumSingleton instance = EnumSingleton.INSTANCE;
EnumSingleton instance1 = EnumSingleton.INSTANCE;
System.out.println(instance==instance1);
}
}
6.序列化
可以利用 指定方法来替换从反序列化流中的数据,枚举类型天然支持反序列化
public class InnerClazzSingletonTest {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException, ClassNotFoundException {
InnerClazzSingleton instance = InnerClazzSingleton.getInstance();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("innerClazzSingleton"));
oos.writeObject(instance);
oos.close();
}
}
class InnerClazzSingleton implements Serializable {
//static final long serialVersionUID = 42L;
private static class InnerClassHolder{
private static InnerClazzSingleton instance = new InnerClazzSingleton();
}
private InnerClazzSingleton(){
if(InnerClassHolder.instance!=null){
throw new RuntimeException("单例不允许多个实例");
}
}
public static InnerClazzSingleton getInstance(){
return InnerClassHolder.instance;
}
}
序列化后输出到文件里
反序列化后可以看到和我们通过getInstance()拿到的对象不一致,单例变成了多例
加入版本号和readResolve防范
static final long serialVersionUID = 42L;
Object readResolve() throws ObjectStreamException {
return InnerClassHolder.instance;
}
7.源码中的应用
// Spring & JDK
java.lang.Runtime
org.springframework.aop.framework.ProxyFactoryBean
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry
org.springframework.core.ReactiveAdapterRegistry



