单例模式
单例模式
什么是单例模式?
- 单例模式是一种创建型设计模式, 让你能够保证一个类只有一个实例, 并提供一个访问该实例的全局节点。
实现方式
- 在类中添加一个私有静态成员变量用于保存单例实例。
- 声明一个公有静态构建方法用于获取单例实例。
- 在静态方法中实现"延迟初始化"。 该方法会在首次被调用时创建一个新对象, 并将其存储在静态成员变量中。 此后该方法每次被调用时都返回该实例。
- 将类的构造函数设为私有。 类的静态方法仍能调用构造函数, 但是其他对象不能调用。
- 检查客户端代码, 将对单例的构造函数的调用替换为对其静态构建方法的调用。
应用场景
- 如果程序中的某个类对于所有客户端只有一个可用的实例, 可以使用单例模式。
- 如果你需要更加严格地控制全局变量, 可以使用单例模式。
java.lang.Runtime应用了单例设计模式。
单例模式优缺点
| 优点 | 缺点 |
|---|---|
| 可以保证一个类只有一个实例。 | 违反了单一职责原则。 |
| 获得一个指向该实例的全局访问节点。 | 单例模式可能掩盖不良设计, 比如程序各组件之间相互了解过多等。 |
| 仅在首次请求单例对象时对其进行初始化。 | 该模式在多线程环境下需要进行特殊处理, 避免多个线程多次创建单例对象。 |
| 单例的客户端代码单元测试可能会比较困难, 因为许多测试框架以基于继承的方式创建模拟对象。 由于单例类的构造函数是私有的, 而且绝大部分语言无法重写静态方法, 所以你需要想出仔细考虑模拟单例的方法。 要么干脆不编写测试代码, 或者不使用单例模式。 |
代码演示
饿汉式(普通)
-
package com.lyc.study.designMode.singleton; /** * 饿汉式: 单例类的实例是在类加载时创建的,会造成资源浪费;线程安全的 * * @author 李云晨 * @date 2022/07/24 14:48 **/ public class Singleton { private static final Singleton instance = new Singleton(); private Singleton () {} public static Singleton getInstance () { return instance; } } class Client { public static void main(String[] args) throws InterruptedException { new Thread(() -> { System.out.println(Singleton.getInstance()); }).start(); new Thread(() -> { System.out.println(Singleton.getInstance()); }).start(); } }
饿汉式(静态代码块)
package com.lyc.study.designMode.singleton;
/**
* 饿汉式: 单例类的实例是在类加载时创建的,会造成资源浪费;线程安全的
*
* @author 李云晨
* @date 2022/07/24 14:48
**/
public class Singleton {
private static Singleton instance;
static {
// 可以定义异常抛出
instance = new Singleton();
}
private Singleton () {}
public static Singleton getInstance () {
return instance;
}
}
class Client {
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
System.out.println(Singleton.getInstance());
}).start();
new Thread(() -> {
System.out.println(Singleton.getInstance());
}).start();
}
}
懒汉式(单线程-Y,多线程-N)
-
package com.lyc.study.designMode.singleton; import java.util.Objects; /** * @author 李云晨 * @date 2022/07/24 14:48 **/ public class Singleton { private static Singleton instance; public String value; private Singleton (String value) { // 以此来模拟慢加载 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } this.value = value; } public static Singleton getInstance (String value) { if (Objects.isNull(instance)) { instance = new Singleton(value); } return instance; } } class Client { public static void main(String[] args) { /** * 若两个值均为实例A,则是单例模式 * 若两个值不等,则创建了两个不同实例 */ new Thread(() -> { Singleton a = Singleton.getInstance("实例A"); System.out.println(a.value); }, "线程A").start(); new Thread(() -> { Singleton b = Singleton.getInstance("实例B"); System.out.println(b.value); }, "线程B").start(); } }
懒汉式(多线程-Y)
package com.lyc.study.designMode.singleton;
import java.util.Objects;
/**
* @author 李云晨
* @date 2022/07/24 14:48
**/
public class Singleton {
/**
* 字段必须声明为volatile,这样双重检查锁才能工作
*/
private volatile static Singleton instance;
public String value;
private Singleton (String value) {
// 以此来模拟慢加载
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.value = value;
}
public static Singleton getInstance (String value) {
/**
* 双重检查锁定 (DCL)
*/
if (Objects.isNull(instance)) {
synchronized (Singleton.class) {
if (Objects.isNull(instance)) {
instance = new Singleton(value);
}
}
}
return instance;
}
}
class Client {
public static void main(String[] args) {
/**
* 若两个值均为实例A,则是单例模式
* 若两个值不等,则创建了两个不同实例
*/
new Thread(() -> {
Singleton a = Singleton.getInstance("实例A");
System.out.println(a.value);
}, "线程A").start();
new Thread(() -> {
Singleton b = Singleton.getInstance("实例B");
System.out.println(b.value);
}, "线程B").start();
}
}
私有静态内部类
package com.lyc.study.designMode.singleton;
/**
* InnerSingleton类不会加载到内存中,只有当有人调用getInstance方法时,才会加载此类并创建单例类实例。
*
* @author 李云晨
* @date 2022/07/24 14:48
**/
public class Singleton {
private Singleton () {}
private static class InnerSingleton {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance () {
return InnerSingleton.INSTANCE;
}
}
class Client {
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
System.out.println(Singleton.getInstance());
}).start();
new Thread(() -> {
System.out.println(Singleton.getInstance());
}).start();
}
}
反射破坏单例
-
package com.lyc.study.designMode.singleton; import java.lang.reflect.Constructor; /** * 反射破坏单例 * * @author 李云晨 * @date 2022/07/24 14:48 **/ public class Singleton { private Singleton() { } private static class InnerSingleton { private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance() { return InnerSingleton.INSTANCE; } } class Client { public static void main(String[] args) { Singleton a = Singleton.getInstance(); Singleton b = null; Constructor<?>[] constructors = Singleton.class.getDeclaredConstructors(); for (Constructor<?> constructor : constructors) { try { constructor.setAccessible(true); b = (Singleton)constructor.newInstance(); } catch (Exception e) { e.printStackTrace(); } break; } // false System.out.println(a == b); } }
枚举
-
package com.lyc.study.designMode.singleton; /** * 枚举实例单例模式 * * @author 李云晨 * @date 2022/07/24 14:48 **/ public enum Singleton { INSTANCE; } class Client { public static void main(String[] args) { Singleton a = Singleton.INSTANCE; Singleton b = Singleton.INSTANCE; System.out.println(a == b); } }
序列化破坏单例
-
package com.lyc.study.designMode.singleton; import java.io.*; /** * 反序列化破坏单例 * * @author 李云晨 * @date 2022/07/24 14:48 **/ public class Singleton implements Serializable { private static final long serialVersionUID = 763534173627372604L; private Singleton () {} private static class InnerSingleton { private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance () { return InnerSingleton.INSTANCE; } } class Client { public static void main(String[] args) { Singleton a = Singleton.getInstance(); Singleton b = null; try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("实例A"))) { out.writeObject(a); } catch (Exception e) { e.printStackTrace(); } try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("实例A"))) { b = (Singleton) in.readObject();; } catch (Exception e) { e.printStackTrace(); } // false System.out.println(a == b); } }
序列化破坏单例(解决方案)
-
package com.lyc.study.designMode.singleton; import java.io.*; /** * 单例类增加readResolve方法 * * @author 李云晨 * @date 2022/07/24 14:48 **/ public class Singleton implements Serializable { private static final long serialVersionUID = 763534173627372604L; private Singleton () {} private static class InnerSingleton { private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance () { return InnerSingleton.INSTANCE; } /** * 当我们通过反序列化readObject()方法获取对象时会去寻找readResolve()方法。 * 如果该方法不存在则直接返回新对象,如果该方法存在则按该方法的内容返回对象。 * 以确保如果我们之前实例化了单例对象,就返回该对象。 * 如果我们之前没有实例化单例对象,则会返回null。 */ protected Object readResolve() { return getInstance(); } } class Client { public static void main(String[] args) { Singleton a = Singleton.getInstance(); Singleton b = null; try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("实例A"))) { out.writeObject(a); } catch (Exception e) { e.printStackTrace(); } try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("实例A"))) { b = (Singleton) in.readObject();; } catch (Exception e) { e.printStackTrace(); } System.out.println(a == b); } }

浙公网安备 33010602011771号