1. 意图
保证一个类只有一个实例, 并提供一个访问该实例的全局节点
2. 动机
- 控制某些共享资源(数据库或文件)的访问权限
- 为该实例提供一个全局访问点
3. 适用性
- 当类只能有一个实例而且客户可以从一个众所周知的访问点访问它时
- 更严格的控制全局变量
4. 结构
5. 效果
1) 对唯一实例的受控访问
2) 获得指向该实例的全局访问节点
3) 仅在首次请求单例对象时进行初始化
4) 违反了单一职责原则。 该模式同时解决了两个问题(在一个方法中进行了创建类和提供类对象的操作)
5) 单例模式可能掩盖不良设计, 比如程序各组件之间相互了解过多等
6) 该模式在多线程环境下需要进行特殊处理, 避免多个线程多次创建单例对象
7) 单例的客户端代码单元测试可能会比较困难, 因为许多测试框架以基于继承的方式创建模拟对象。 由于单例类的构造函数是私有的, 而且绝大部分语言无法重写静态方法, 所以你需要想出仔细考虑模拟单例的方法。 要么干脆不编写测试代码, 或者不使用单例模式
6. 代码实现
Singleton_singlethread.java: 单例(单线程)
package singleton; /** * @author GaoMing * @date 2021/7/18 - 19:13 */ public class Singleton_singlethread { private static Singleton_singlethread instance; public String value; private Singleton_singlethread(String value) { // The following code emulates slow initialization. try { Thread.sleep(1000); } catch (InterruptedException ex) { ex.printStackTrace(); } this.value = value; } public static Singleton_singlethread getInstance(String value) { if (instance == null) { instance = new Singleton_singlethread(value); } return instance; } }
DemoSingleThread.java: 客户端代码
package singleton; /** * @author GaoMing * @date 2021/7/18 - 19:16 */ public class DemoSingleThread { public static void main(String[] args) { System.out.println("If you see the same value, then singleton was reused (yay!)" + "\n" + "If you see different values, then 2 singletons were created (booo!!)" + "\n\n" + "RESULT:" + "\n"); Singleton_singlethread singleton = Singleton_singlethread.getInstance("FOO"); Singleton_singlethread anotherSingleton = Singleton_singlethread.getInstance("BAR"); System.out.println(singleton.value); System.out.println(anotherSingleton.value); } }
执行结果
If you see the same value, then singleton was reused (yay!) If you see different values, then 2 singletons were created (booo!!) RESULT: FOO FOO
Singleton_multithread.java: 单例(多线程安全单例)
package singleton; /** * @author GaoMing * @date 2021/7/18 - 19:12 */ public final class Singleton_multithread { private static volatile Singleton_multithread instance; public String value; private Singleton_multithread(String value) { this.value = value; } public static Singleton_multithread getInstance(String value) { // The approach taken here is called double-checked locking (DCL). It // exists to prevent race condition between multiple threads that may // attempt to get singleton instance at the same time, creating separate // instances as a result. // // It may seem that having the `result` variable here is completely // pointless. There is, however, a very important caveat when // implementing double-checked locking in Java, which is solved by // introducing this local variable. // // You can read more info DCL issues in Java here: // https://refactoring.guru/java-dcl-issue Singleton_multithread result = instance; if (result != null) { return result; } synchronized(Singleton_multithread.class) { if (instance == null) { instance = new Singleton_multithread(value); } return instance; } } }
DemoMultiThread.java: 客户端代码
package singleton; /** * @author GaoMing * @date 2021/7/18 - 19:30 */ public class DemoMultiThread { public static void main(String[] args) { System.out.println("If you see the same value, then singleton was reused (yay!)" + "\n" + "If you see different values, then 2 singletons were created (booo!!)" + "\n\n" + "RESULT:" + "\n"); Thread threadFoo = new Thread(new ThreadFoo()); Thread threadBar = new Thread(new ThreadBar()); threadFoo.start(); threadBar.start(); } static class ThreadFoo implements Runnable { @Override public void run() { Singleton_multithread singleton = Singleton_multithread.getInstance("FOO"); System.out.println(singleton.value); } } static class ThreadBar implements Runnable { @Override public void run() { Singleton_multithread singleton = Singleton_multithread.getInstance("BAR"); System.out.println(singleton.value); } } }
执行结果
If you see the same value, then singleton was reused (yay!) If you see different values, then 2 singletons were created (booo!!) RESULT: BAR BAR
7. 与其他模式的关系
- 外观模式类通常可以转换为单例模式类, 因为在大部分情况下一个外观对象就足够了
- 抽象工厂模式、 生成器模式和原型模式都可以用单例来实现
8. 已知应用
- java.lang.Runtime#getRuntime()
- java.awt.Desktop#getDesktop()
- java.lang.System#getSecurityManager()
识别方法: 单例可以通过返回相同缓存对象的静态构建方法来识别。