Java单例模式
再孬再好,就你一个
单例模式
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
注意:
-
单例类限制类的实例个数,保证类的实例在JVM的世界里只有一个类的实例对象。
- 单例类必须自己创建自己的唯一实例。
-
单例类必须提供一个全局性的公共访问方式获取单例类的实例对象
-
单例类必须给所有其他对象提供这一实例。
介绍
意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
主要解决:一个全局使用的类频繁地创建与销毁。
何时使用:当您想控制实例数目,节省系统资源的时候。
如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
关键代码:构造函数是私有的。
应用场景: java.lang.Runtime类、日志、驱动器、缓存、线程池等l
实现
我们将创建一个 SingleObject 类。SingleObject 类有它的私有构造函数和本身的一个静态实例。
SingleObject 类提供了一个静态方法,供外界获取它的静态实例。SingletonPatternDemo,我们的演示类使用 SingleObject 类来获取 SingleObject 对象。
步骤 1
创建一个 Singleton 类,SingleObject.java
步骤 2
从 singleton 类获取唯一的对象,测试类SingletonPatternDemo.java
输出效果:
不过java实现单例模式有好几种方式,简单绘图一张https://www.processon.com/view/link/5ebb9c087d9c08156c3be39a,如下:
我就一一列举:
1. 饿汉式初始化 Eager initialization
在饿汉初始化模式下,单例类的实例是在类加载的时候完成了创建,这是最简单的创建单例的方式,但它有个缺陷就是客户端还没使用已创建好的单例实例。下面就是实现了静态初始化的单例类
2. 静态块初始化 Static block initialization
静态块初始化的实现单例和饿汉式初始化类似,但它在创建类实例的时候提供了异常处理
3. 延迟初始化 Lazy Initialization
延迟初始化意味着创建单例的是时机不是在类加载过程了,而是在全局访问方法里实现单例模式的实例创建,就是需要时再创建
此种实现方式在单线程环境下表现很好,但是现实中往往是多线程环境,故有了下面的其他实现方式
4. 线程安全型单例Thread Safe Singleton
有一种简单创建线程安全的单例,就是给全局访问方法加上关键字synchronized (锁的一种),它可以确保只有一个线程执行获取单例的全局访问方法。
Above implementation works fine and provides thread-safety but it reduces the performance because of the cost associated with the synchronized method, although we need it only for the first few threads who might create the separate instances (Read: Java Synchronization). To avoid this extra overhead every time, double checked locking principle is used. In this approach, the synchronized block is used inside the if condition with an additional check to ensure that only one instance of a singleton class is created.
该实现确实实现了线程安全的单例,可同步方法对性能有影响,可改造为双重检测锁
5. 内部静态辅助类 Inner Static Helper Class
借助内部静态类创建单例
特别注意,当单例类被加载的时候,内部辅助类还没有加载到内存,当有客户端调用公共访问getSingletonInstance方法时内部类才被加载,然后创建单例对象。
一句话:私有内部静态类负责创建单例类的实例对象,且不需要同步机制
7. 序列化单例 Serialization and Singleton
有时在分布式系统中,为了在文件系统中存放单实例状态,我们需要在单例类中实现序列化接口(Serializable interface),下面是一个实现了序列化接口的单实例类:
.序列化的单实例进行反序列化时会出现问题,创建了一个新的类实例对象,如下代码:
输出结果:
可以通过在单实例类的定义中加上
注意,反序列化会判断是否有readResolve方法
这样当JVM从内存中反序列化地"组装"一个新对象时,就会自动调用这个 readResolve方法来返回我们指定好的对象了, 单例模式规则也就得到了保证.
此时非常好,JVM世界里只有一个单实例对象
8. 枚举单例 Enum Singleton
用枚举的方式实现单例模式也不失为一种好方法,它可以保证任何一个枚举值只被实例化一次
测试枚举型单例代码:
输出结果
9. 使用反射 ,魔方
可以使用反射,注意,它和没有使用readResolve()方法的序列化单例模式类似,会破坏单例模式,JVM的世界里会有多个实例对象
执行输出结果(好可怕破坏单例模式)
发现两个实例并不相等,反射很强大,被广泛用于像Spring、Hibernate、Mybatis等框架中,普通开发禁止使用。加上我的反射一文,从此以后,你可以光明正大的说,私有属性、私有构造器、私有方法可以访问兼修改了
小结: 单例模式实现多种多样,实际场景自行把控选择。代码已进入github: https://github.com/dongguangming/design-pattern/tree/master/src/code/singleton
参考:
0. 单例模式 https://baike.baidu.com/item/%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F/5946627
1. Java Singleton Design Pattern Best Practices with Examples https://www.journaldev.com/1377/java-singleton-design-pattern-best-practices-examples
2. 要FQ https://javarevisited.blogspot.com/2012/12/how-to-create-thread-safe-singleton-in-java-example.html
3. double checked locking (同样要FQ)http://jeremymanson.blogspot.com/2008/05/double-checked-locking.html
4. The "Double-Checked Locking is Broken" Declaration?
https://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html