单例模式

单例模式

单例模式

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供了一个全局访问点来访问该实例。

注意:

单例类只能有一个实例。

单例类必须自己创建自己的唯一实例。

单例类必须给所有其他对象提供这一实例。

1. 饿汉式(静态变量)

public class Singleton {

private static final Singleton INSTANCE = new Singleton();

private Singleton() {}

public static Singleton getInstance() {

return INSTANCE;

}

}

保证单例的点:静态变量在类加载时初始化,只初始化一次。

线程安全:因为类加载本身就是线程安全的。

2. 饿汉式(静态代码块)

public class Singleton {

private static final Singleton INSTANCE;

static {

INSTANCE = new Singleton();

}

private Singleton() {}

public static Singleton getInstance() {

return INSTANCE;

}

}

保证单例的点:和上面一样,只是初始化写到了静态代码块里。

3. 懒汉式(线程安全同步方法 )

public class Singleton {

private static Singleton instance;

private Singleton4() {}

public static synchronized Singleton getInstance() {

if (instance == null) {

instance = new Singleton4();

}

return instance;

}

}

保证单例的点:synchronized保证同一时刻只有一个线程能进入方法。

缺点:每次都同步,性能差。

4. 双重检查锁(DCL)

public class Singleton {

private static volatile Singleton instance; // volatile 一定要有

private Singleton() {}

public static Singleton getInstance() {

if (instance == null) { // 还没创建则创建

synchronized (Singleton.class) { // 加锁

if (instance == null) { // 拿到锁的时候可能有其他线程先拿到锁已经创建了,这个时候再判断一次是否已经创建

instance = new Singleton();

}

}

}

return instance;

}

}

保证单例的点:

外层第一次检查instance == null避免加锁开销;

内层加锁后再检查一次,确保只有一个实例;

volatile禁止指令重排序,防止半初始化问题。

5. 静态内部类方式

public class Singleton {

private Singleton() {}

private static class Holder {

private static final Singleton INSTANCE = new Singleton();

}

public static Singleton getInstance() {

return Holder.INSTANCE;

}

}

保证单例的点:

类加载是懒加载的,Holder类不会初始化,直到调用getInstance();

JVM保证类初始化是线程安全的。

6. 枚举单例

public enum Singleton {

INSTANCE;

public void doSomething() {

System.out.println("Doing something...");

}

}

保证单例的点:

JVM保证每个枚举实例在内存中只创建一次;

防止反射攻击;

防止反序列化破坏单例。

7. Spring容器管理的单例

在Spring里,默认情况下,Bean是单例的(@Scope("singleton")):

@Service

public class MyService {

}

保证单例的点:

Spring在IOC容器初始化阶段实例化Bean,并放到单例池;

后续通过名字拿对象,拿的都是同一个。

8. 单例模式的常见问题

8.1反射破坏单例

反射可以访问私有构造器,从而绕开正常流程,直接创建多个实例。

Singleton instance1 = Singleton.getInstance();

Constructor constructor = Singleton.class.getDeclaredConstructor();

constructor.setAccessible(true);

Singleton instance2 = constructor.newInstance();

System.out.println(instance1 == instance2); // false!

可以在构造方法中加一个判断,防止二次实例化:

public class Singleton {

private static final Singleton INSTANCE = new Singleton();

private Singleton() {

if (INSTANCE != null) {

throw new RuntimeException("单例对象,禁止反射创建!");

}

}

public static Singleton getInstance() {

return INSTANCE;

}

}

这样即使通过反射调用,也会抛异常!

8.2 序列化破坏单例

如果单例对象实现了Serializable接口,在序列化 -> 反序列化过程中,会创建出一个新的对象实例。

Singleton instance1 = Singleton.getInstance();

ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton.obj"));

oos.writeObject(instance1);

ObjectInputStream ois = new ObjectInputStream(new FileInputStream("singleton.obj"));

Singleton instance2 = (Singleton) ois.readObject();

System.out.println(instance1 == instance2); // false!

可以加一个 readResolve() 方法:

private Object readResolve() throws ObjectStreamException {

return INSTANCE;

}

这样反序列化时不会新建对象,而是直接返回已有的实例!

规则:

readResolve()方法在ObjectInputStream读取对象后自动调用

返回的对象替换掉反序列化出来的新对象

为什么推荐枚举单例

枚举类的实例创建由JVM保证,天然防止了:

反射破坏

反序列化破坏

多线程破坏

总结

序号

实现方式

保证单例的机制

是否懒加载

线程安全

备注

1

饿汉式(静态变量)

类加载时创建

简单,推荐

2

饿汉式(静态代码块)

类加载时创建

可加复杂逻辑

3

懒汉式(线程安全同步方法)

延迟初始化+同步方法

性能差

4

懒汉式(DCL双重检查)

volatile + 双检查锁

推荐,最经典

5

静态内部类

JVM加载机制

推荐,优雅

6

枚举单例

JVM保证枚举单例

最推荐,防反射和反序列化

7

Spring容器管理单例

Spring的IOC容器

可配置

Spring默认就是单例Bean

相关推荐

高考被录取了不想去读,怎么退档?给招生办打电话可以退档吗?
多媒体视频制作包括哪些内容?
beat365手机版客户端ios

多媒体视频制作包括哪些内容?

📅 07-02 👁️ 293
《乱斗西游2》手游英雄后羿图鉴
det365在线平台

《乱斗西游2》手游英雄后羿图鉴

📅 07-20 👁️ 7113