JAVA设计模式之单例模式
要点单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式 。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
- 单例类只能有一个实例
- 单例类必须自己创建自己的唯一实例
- 单例类必须给所有其他对象提供这一实例
主要解决: 一个全局使用的类被频繁的创建和销毁。
何时使用: 当你想控制实例数目,节省系统资源的时候。
如何解决: 判断系统是否已经有这个单例,如果有则返回,如果没有则创建
关键代码: 构造行数是私有的。
优点:
1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如一般网页的头部菜单和尾部导航)。
2、避免对资源的多重占用(比如写文件操作)。
实现方式
1.懒汉式,线程不安全
-
是否Lazy 初始化: 是,=>在使用的时候才进行初始化,为Lazy 初始化
-
是否多线程安全: 否,=>因为没有加锁,synchronized
-
实现难度: 易
-
代码demo
public class LazyModeUnsafeSingleton { /** 在类加载的时候创建一次实例 */ private static LazyModeUnsafeSingleton instance; /** 让构造函数为 private,这样该类就不会被实例化 */ private LazyModeUnsafeSingleton() { } /** 获取唯一可用的对象 */ public static LazyModeUnsafeSingleton newInstance() { if (instance == null) { instance = new LazyModeUnsafeSingleton(); } return instance; } public void showMessage() { System.out.println("Hello World!"); } }
2.懒汉式,线程安全
-
是否Lazy 初始化: 是,=>在使用的时候才进行初始化,为Lazy 初始化
-
是否多线程安全: 是,=>加锁,保证同时只有一个线程调用该方法
-
实现难度: 易
-
优点: 第一次调用才初始化,避免内存浪费
-
缺点: 必须加锁 synchronized 才能保证单例,但加锁会影响效率
-
代码 demo
public class LazyModeSingleton { /** 在类加载的时候创建一次实例 */ private static LazyModeSingleton instance; /** 让构造函数为 private,这样该类就不会被实例化 */ private LazyModeSingleton() { } /** 加锁,保证同时只有一个线程调用该方法,获取唯一可用的对象 */ public static synchronized LazyModeSingleton newInstance() { if (instance == null) { instance = new LazyModeSingleton(); } return instance; } public void showMessage() { System.out.println("Hello World!"); } }
3.饿汉式,线程安全
-
是否Lazy 初始化: 否,=>类在装载的时候进行初始化,非Lazy初始化
-
是否多线程安全: 是,=>基于 classloder 机制避免了多线程的同步问题
-
实现难度: 易
-
优点: 没有加锁,执行效率会提高。
-
缺点: 类加载时就初始化,浪费内存
-
代码demo
public class HungryModeSingleton {/** 在类加载的时候创建一次实例 */ private static HungryModeSingleton instance = new HungryModeSingleton(); /** 让构造函数为 private,这样该类就不会被实例化 */ private HungryModeSingleton() { } /** 获取唯一可用的对象 */ public static HungryModeSingleton newInstance() { return instance; } public void showMessage() { System.out.println("Hello World!"); } }
4.双检锁/双重校验锁(DCL,即 double-checked locking)
-
是否Lazy 初始化: 是,=>类在装载的时候进行初始化,非Lazy初始化
-
是否多线程安全: 是,=>因为有加锁,synchronized
-
实现难度: 较复杂
-
描述: 这种方式采用双锁机制,安全且在多线程情况下能保持高性能。getInstance() 的性能对应用程序很关键
-
代码demo
public class DoubleCheckedLockModeSingleton { /** 在类加载的时候创建一次实例 volatile:变量修饰符,直接读写主存 */ private volatile static DoubleCheckedLockModeSingleton instance = null; /** 让构造函数为 private,这样该类就不会被实例化 */ private DoubleCheckedLockModeSingleton() { } /** 获取唯一可用的对象 */ public static DoubleCheckedLockModeSingleton newInstance() { // 为空表示第一次调用该方法 if (instance == null) { // 保证并发null进入该方法块的时候,设置锁 synchronized (DoubleCheckedLockModeSingleton.class) { if (instance == null) { instance = new DoubleCheckedLockModeSingleton(); } } } return instance; } public void showMessage() { System.out.println("Hello World!"); }
}
5.登记式/静态内部类
-
是否Lazy 初始化: 是,=>类在装载的时候进行初始化,非Lazy初始化
-
是否多线程安全: 是,=>因为没有加锁,synchronized
-
实现难度: 一般
-
描述: 种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。
这种方式同样利用了 classloder 机制来保证初始化 instance 时只有一个线程,它跟第 3 种方式不同的是:第 3 种方式只要 Singleton 类被装载了,那么 instance 就会被实例化(没有达到 lazy loading 效果),而这种方式是 Singleton 类被装载了,instance 不一定被初始化。因为 SingletonHolder 类没有被主动使用,只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance。想象一下,如果实例化 instance 很消耗资源,所以想让它延迟加载,另外一方面,又不希望在 Singleton 类加载时就实例化,因为不能确保 Singleton 类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化 instance 显然是不合适的。这个时候,这种方式相比第 3 种方式就显得很合理。 -
代码demo
public class StaticModeSingleton { private static class StaticModeSingletonHolder{ private static final StaticModeSingleton INSTANCE = new StaticModeSingleton(); } /** 让构造函数为 private,这样该类就不会被实例化 */ private StaticModeSingleton() { } /** 获取唯一可用的对象 */ public static StaticModeSingleton newInstance() { return StaticModeSingletonHolder.INSTANCE; } public void showMessage() { System.out.println("Hello World!"); }
}
测试代码
public static void main(String[] args) {
// 获取唯一可用的对象
StaticModeSingleton staticModeSingleton = StaticModeSingleton.newInstance();
// 执行动作
staticModeSingleton.showMessage();
}
一般情况下,不建议使用第 1 种和第 2 种懒汉方式,建议使用第 3 种饿汉方式。只有在要明确实现 lazy loading 效果时,才会使用第 5 种登记方式。如果有其他特殊的需求,可以考虑使用第 4 种双检锁方式