目录
- 一、啥是单例模式?
- 二、为什么要用单例模式?
- 三、单例模式怎么实现?
- 1. 饿汉式:先下手为强!
- 2. 懒汉式:用的时候再创建!
- 3. 枚举:最简单最安全的单例!
- 四、单例模式的应用场景
- 五、单例模式的破坏与防御
- 六、总结
我的其他文章也讲解的比较有趣,如果喜欢博主的讲解方式,可以多多支持一下,感谢!
比如: synchronized 关键字:线程同步的“VIP 包间”
这篇文章带你详细认识一下设计模式中的单例模式
想象一下,你有一个特别宝贝的遥控器 ,只能控制你家的电视。如果家里有好多遥控器,那不就乱套了吗?单例模式就像这个遥控器一样,保证一个类只能创建一个对象,而且这个对象是全局唯一的!
简单来说,单例模式就是:一个类只有一个实例,而且到处都能访问它!
单例模式有很多种实现方式,我们一个个来看:
饿汉式就像一个急性子,在类加载的时候就创建好对象了,不管你用不用,它都先准备好!
方式1:静态常量
public class Singleton {
// 1. 私有构造方法,防止别人乱new ♀️
private Singleton() {
System.out.println("Singleton 构造方法被调用了!"); // 看看啥时候被调用的
}
// 2. 在内部创建一个静态常量,直接new一个对象
private static final Singleton instance = new Singleton();
// 3. 提供一个公共的静态方法,让别人来拿这个对象
public static Singleton getInstance() {
System.out.println("getInstance() 方法被调用了!"); // 看看啥时候被调用的
return instance;
}
public void doSomething() {
System.out.println("Singleton 对象正在工作! ♀️");
}
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
s1.doSomething();
Singleton s2 = Singleton.getInstance(); // 再次获取
System.out.println(s1 == s2); // 看看是不是同一个对象
}
}
输出结果:
Singleton 构造方法被调用了! // 类加载时就调用了
getInstance() 方法被调用了!
Singleton 对象正在工作! ♀️
getInstance() 方法被调用了!
true // s1 和 s2 是同一个对象
优点: 实现简单,线程安全,不用担心多线程问题。
缺点: 类加载的时候就创建对象,如果一直不用,就浪费内存了 。
方式2:静态代码块
public class Singleton {
private Singleton() {
System.out.println("Singleton 构造方法被调用了!");
}
private static Singleton instance;
static {
System.out.println("静态代码块被执行了!");
instance = new Singleton();
}
public static Singleton getInstance() {
System.out.println("getInstance() 方法被调用了!");
return instance;
}
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
System.out.println(s1 == s2);
}
}
输出结果:
静态代码块被执行了!
Singleton 构造方法被调用了!
getInstance() 方法被调用了!
getInstance() 方法被调用了!
true
说明: 这种方式和静态常量方式差不多,都是在类加载的时候创建对象,优缺点也一样。
懒汉式就像一个懒家伙,只有在你需要的时候才创建对象,实现了延迟加载!
方式1:线程不安全
public class Singleton {
private Singleton() {
System.out.println("Singleton 构造方法被调用了!");
}
private static Singleton instance;
public static Singleton getInstance() {
System.out.println("getInstance() 方法被调用了!");
if (instance == null) {
System.out.println("instance 为 null,准备创建对象!");
instance = new Singleton();
}
return instance;
}
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
System.out.println(s1 == s2);
}
}
输出结果:
getInstance() 方法被调用了!
instance 为 null,准备创建对象!
Singleton 构造方法被调用了!
getInstance() 方法被调用了!
true
优点: 实现了延迟加载,节省了内存。
缺点: 在多线程环境下,不安全!多个线程可能同时进入 if (instance == null)
,导致创建多个对象 。
方式2:线程安全(同步方法)
public class Singleton {
private Singleton() {
System.out.println("Singleton 构造方法被调用了!");
}
private static Singleton instance;
public static synchronized Singleton getInstance() { // 加了 synchronized 关键字
System.out.println("getInstance() 方法被调用了!");
if (instance == null) {
System.out.println("instance 为 null,准备创建对象!");
instance = new Singleton();
}
return instance;
}
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
System.out.println(s1 == s2);
}
}
优点: 解决了线程安全问题。
缺点: 性能太差!每次调用 getInstance()
都要加锁,太慢了 。
方式3:双重检查锁(Double-Checked Locking)
public class Singleton {
private Singleton() {
System.out.println("Singleton 构造方法被调用了!");
}
private static volatile Singleton instance; // volatile 保证可见性和有序性
public static Singleton getInstance() {
System.out.println("getInstance() 方法被调用了!");
if (instance == null) { // 第一次检查
synchronized (Singleton.class) { // 加锁
System.out.println("进入 synchronized 代码块!");
if (instance == null) { // 第二次检查
System.out.println("instance 仍然为 null,准备创建对象!");
instance = new Singleton();
}
}
}
return instance;
}
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
System.out.println(s1 == s2);
}
}
优点: 兼顾了线程安全和性能,只有在第一次创建对象的时候才加锁。
缺点: 实现比较复杂,需要 volatile
关键字来防止指令重排序。
方式4:静态内部类
public class Singleton {
private Singleton() {
System.out.println("Singleton 构造方法被调用了!");
}
// 静态内部类
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton(); // 在内部类中创建实例
static {
System.out.println("SingletonHolder 静态代码块被执行了!");
}
}
public static Singleton getInstance() {
System.out.println("getInstance() 方法被调用了!");
return SingletonHolder.INSTANCE; // 返回内部类的实例
}
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
System.out.println(s1 == s2);
}
}
输出结果:
getInstance() 方法被调用了!
SingletonHolder 静态代码块被执行了!
Singleton 构造方法被调用了!
getInstance() 方法被调用了!
true
优点: 线程安全,延迟加载,实现简单,强烈推荐!
缺点: 稍微有点难理解。
public enum Singleton {
INSTANCE; // 唯一的实例
public void doSomething() {
System.out.println("枚举单例正在工作! ");
}
public static void main(String[] args) {
Singleton.INSTANCE.doSomething();
}
}
优点: 实现简单,线程安全,防止反射攻击和序列化攻击,绝对安全!
缺点: 不能延迟加载。
单例模式虽然好,但是也可能被破坏!
防御方法:
readResolve()
方法,在反序列化时返回已存在的实例。希望这篇文章能让你彻底理解单例模式!
看完请看:(二)趣学设计模式 之 工厂方法模式!