单例模式Singleton
https://refactoringguru.cn/design-patterns/singleton
https://www.journaldev.com/1377/java-singleton-design-pattern-best-practices-examples
https://blog.csdn.net/hzygcs/article/details/88627399
https://blog.csdn.net/qq_40794973/article/details/104165984
https://www.cnblogs.com/chiclee/p/9097772.html
单例模式是一种创建型设计模式, 让你能够保证一个类只有一个实例, 并提供一个访问该实例的全局节点。
1.问题
单例模式同时解决了两个问题, 所以违反了单一职责原则:
-
保证一个类只有一个实例。 为什么会有人想要控制一个类所拥有的实例数量? 最常见的原因是控制某些共享资源 (例如数据库或文件) 的访问权限。它的运作方式是这样的: 如果你创建了一个对象, 同时过一会儿后你决定再创建一个新对象, 此时你会获得之前已创建的对象, 而不是一个新对象。注意, 普通构造函数无法实现上述行为, 因为构造函数的设计决定了它必须总是返回一个新对象。
-
为该实例提供一个全局访问节点。 还记得你 (好吧, 其实是我自己) 用过的那些存储重要对象的全局变量吗? 它们在使用上十分方便, 但同时也非常不安全, 因为任何代码都有可能覆盖掉那些变量的内容, 从而引发程序崩溃。和全局变量一样, 单例模式也允许在程序的任何地方访问特定对象。 但是它可以保护该实例不被其他代码覆盖。还有一点: 你不会希望解决同一个问题的代码分散在程序各处的。 因此更好的方式是将其放在同一个类中, 特别是当其他代码已经依赖这个类时更应该如此。
如今, 单例模式已经变得非常流行, 以至于人们会将只解决上文描述中任意一个问题的东西称为单例。
2.解决方案
所有单例的实现都包含以下两个相同的步骤:
- 将默认构造函数设为私有, 防止其他对象使用单例类的
new
运算符。 - 新建一个静态构建方法作为构造函数。 该函数会 “偷偷” 调用私有构造函数来创建对象, 并将其保存在一个静态成员变量中。 此后所有对于该函数的调用都将返回这一缓存对象。
如果你的代码能够访问单例类, 那它就能调用单例类的静态方法。 无论何时调用该方法, 它总是会返回相同的对象。
3.真实世界类比
政府是单例模式的一个很好的示例。 一个国家只有一个官方政府。 不管组成政府的每个人的身份是什么, “某政府” 这一称谓总是鉴别那些掌权者的全局访问节点。
4.单例模式结构
5.单例模式的写法与破解
1.饿汉式(静态常量)[可用]
/**
* 饿汉式(静态常量)(可用)
*/
public class Singleton {
private final static Singleton instance = new Singleton();
// 构造函数私有化
private Singleton() {
//....
}
public static Singleton getInstance() {
return instance;
}
}
- 线程安全性: 实例在类加载的时候即被实例化,因此线程安全。
- 是否懒加载: 没有延迟加载,如果长时间没用到这个实例,则会造成内存的浪费。
- 性能: 性能比较好
2.饿汉式(静态代码块)[可用]
/**
* 饿汉式(静态代码块)(可用)
*/
public class Singleton {
private final static Singleton instance;
static {
// 构造对象可能还会从配置文件读取配置
Properties prop = new Properties();
try {
prop.load(Singleton.class.getClassLoader().getResourceAsStream("single.properties"));
} catch (IOException e) {
throw new RuntimeException("加载配置文件single.properties失败");
}
String name = prop.getProperty("jdbc.name");
// 构造对象
instance = new Singleton();
}
// 私有构造函数
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
### single.properties
jdbc.name=root
jdbc.password=123456
/**
* 写法不当容易产生NPE
*/
public class Singleton {
static {
instance = new Singleton();
}
private static Singleton instance = null;//应该写在静态代码块前面
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
public static void main(String[] args) {
System.out.println(getInstance().hashCode());
}
}
- 线程安全性: 实例在类加载的时候即被实例化,因此线程安全。
- 是否懒加载: 没有延迟加载,如果长时间没用到这个实例,则会造成内存的浪费。
- 性能: 性能比较好
3.懒汉式(线程不安全)[不可用]
/**
* 懒汉式(线程不安全)
*/
public class Singleton {
private static Singleton instance;
private Singleton() {
//...
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
//验证线程安全
public class Singleton {
private static Singleton instance;
private Singleton() {
//...
}
public static Singleton getInstance() {
if (instance == null) {
try {
TimeUnit.MICROSECONDS.sleep(10);
} catch (InterruptedException e) {
//e.printStackTrace();
}
instance = new Singleton();
}
return instance;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable<Singleton> task = Singleton::getInstance;
ExecutorService executor = Executors.newFixedThreadPool(2);
Future<Singleton> f1 = executor.submit(task);
Future<Singleton> f2 = executor.submit(task);
Singleton s1 = f1.get();
Singleton s2 = f2.get();
System.out.println(s1);
System.out.println(s2);
System.out.println(s1 == s2);
executor.shutdown();
}
}
- 线程安全性:单线程下没有问题,多线程下有可能会生成多个实例,因此线程不安全。
- 问题出现在
if (null == instance)
这一步,多线程环境中可能会有多个线程同时拿到instance的值为null。
- 问题出现在
- 是否懒加载: 懒加载
- 性能: 性能比较好
4.懒汉式(线程安全,同步方法)[不推荐]
/**
* 懒汉式(线程安全)(不推荐)
*/
public class Singleton {
private static Singleton instance;
private Singleton() {
//...
}
//多个线程无法并行执行,不能及时的响应
public synchronized static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
- 线程安全性: 线程安全
- 是否懒加载: 懒加载
- 性能:
synchronized
修饰方法,使得多线程执行退化为串行执行,性能差
5.懒汉式(线程不安全,同步代码块)[不可用]
/**
* 懒汉式(线程不安全)(不推荐)
*/
public class Singleton {
private static Singleton instance;
private Singleton() {
//...
}
public static Singleton getInstance() {
//并没有解决线程安全问题
if (instance == null) {
//这里虽然是同步的,但是还是会多次运行
synchronized (Singleton.class) {
instance = new Singleton();
}
}
return instance;
}
}
- 线程安全性:单线程下没有问题,多线程下有可能会生成多个实例,因此线程不安全。
- 问题同样出现在
if (null == instance)
这一步
- 问题同样出现在
- 是否懒加载: 懒加载
- 性能: 性能一般
6.DCL+Volatile [可用]
引入双重锁,即Double-Check-Locking。同时引入volatile关键字修饰实例对象,这是为了避免因JVM指令重排序可能导致的空指针异常。因为当线程执行到第一个if (null == instance)时,代码可能读取到instance不为null,但此时instance引用的对象可能还没有完成初始化。
/**
* 双重检查(推荐面试使用)
*/
public class Singleton {
//注意volatile
//1.可见性
//2.防止重排序
private volatile static Singleton instance;
private Singleton() {
//...
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
//虽然可能进来多个,但是我再做一次检查即可避免多次创建实例
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
为什么要double-check
因为进行一次加锁和解锁是需要付出对应的代价的,而进行两次判断,就可以避免多次加锁与解锁操作,同时也保证了线程安全。
但是,如果进行大数据的操作,加锁操作将成为一个性能的瓶颈。
为什么要用volatile
就是要你懂Java中volatile关键字实现原理:http://www.cnblogs.com/xrq730/p/7048693.html
- 线程安全性: 线程安全性
- 是否懒加载: 懒加载
- 性能: 性能较好
7.Holder模式(静态内部类) [推荐]
声明类时,成员变量中不声明实例变量,而是放到静态内部类中。这种方式和懒汉式有些相似,它们都采用了类装载的机制来保证初始化实例时只有一个线程,不同的是,Holder单例模式是将实例的初始化放到了静态类中去实现,从而实现了懒加载。
/**
* 静态内部类方式,可用
*/
public class Singleton {
private Singleton() {
//...
}
// JVM类加载的性质,保证了即便多个线程同时访问这个SingletonInstance,也不会创建多个实例
// 在内部类被加载和初始化时,才创建INSTANCE实例对象
private static class SingletonInstance {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonInstance.INSTANCE;
}
}
- 线程安全性: 线程安全性
- 是否懒加载: 懒加载
- 性能: 性能好
8.序列化
有时在分布式系统中,我们需要在Singleton类中实现Serializable接口,以便我们可以将其状态存储在文件系统中,并在以后的某个时间点检索它。
public class Singleton implements Serializable {
private static final long serialVersionUID = -1291257263696872928L;
private Singleton() {
}
static class SingletonHelper {
private static final Singleton instance = new Singleton();
}
static Singleton getInstance() {
return SingletonHelper.instance;
}
}
序列化单例类的问题在于,每当反序列化它时,它将创建该类的新实例。
package org.example.jvm01;
import java.io.*;
public class SingletonSerializedTest {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Singleton instanceOne = Singleton.getInstance();
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("filename.ser"));
out.writeObject(instanceOne);
out.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream("filename.ser"));
Singleton instanceTwo = (Singleton) in.readObject();
in.close();
System.out.println("instanceOne hashCode = " + instanceOne.hashCode());
System.out.println("instanceTwo hashCode = " + instanceTwo.hashCode());
}
}
结果
instanceOne hashCode = 325040804
instanceTwo hashCode = 764977973
9.枚举 [最佳]
枚举成员默认都被final、public、static修饰,当使用枚举类型成员时,直接使用枚举名称调用成员即可。
为什么要用枚举实现单例模式(避免反射、序列化问题) https://www.cnblogs.com/chiclee/p/9097772.html
写法一
/** * 枚举单例(推荐生产实践中使用) */ public enum Singleton { INSTANCE; /** * 类中的方法 */ public Connection jdbcConnection() { String user = "root"; String password = "123456"; String url = "jdbc:mysql://......"; String className = "com.mysql.cj.jdbc.Driver"; try { Class.forName(className); return DriverManager.getConnection(url, user, password); } catch (ClassNotFoundException | SQLException e) { throw new RuntimeException(e.getMessage()); } } }
public class Main { public static void main(String[] args) { Connection conn = Singleton.INSTANCE.jdbcConnection(); } }
写法二
public class SingletonExample { // 私有构造函数 private SingletonExample() { } public static SingletonExample getInstance() { return Singleton.INSTANCE.getInstance(); } private enum Singleton { INSTANCE; private SingletonExample singleton; // JVM保证这个方法绝对只调用一次 Singleton() { singleton = new SingletonExample(); } public SingletonExample getInstance() { return singleton; } } }
- 线程安全性: 线程安全性
- 是否懒加载: 懒加载
- 性能: 性能好
10.破解单例模式
反射可用于销毁所有上述单例实现方法。
10.1.反射
10.1.1.反射非枚举单例
public class ReflectionSingletonTest {
public static void main(String[] args) {
Singleton instanceOne = Singleton.getInstance();
Singleton instanceTwo = null;
try {
Constructor<?>[] declaredConstructors = Singleton.class.getDeclaredConstructors();
for (Constructor<?> declaredConstructor : declaredConstructors) {
declaredConstructor.setAccessible(true);
instanceTwo = (Singleton)declaredConstructor.newInstance();
break;
}
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
System.out.println("instanceOne.hashCode():" + instanceOne.hashCode());
System.out.println("instanceTwo.hashCode():" + instanceTwo.hashCode());
}
}
10.1.2.反射枚举单例
public enum Singleton implements Serializable {
INSTANCE;
public static void doSomething(){
//do something
}
}
public class ReflectionSingletonTest {
public static void main(String[] args) {
Singleton instanceOne = Singleton.INSTANCE;
Singleton instanceTwo = null;
try {
Constructor<?>[] declaredConstructors = Singleton.class.getDeclaredConstructors();
for (Constructor<?> declaredConstructor : declaredConstructors) {
declaredConstructor.setAccessible(true);
instanceTwo = (Singleton)declaredConstructor.newInstance();
break;
}
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
System.out.println("instanceOne.hashCode():" + instanceOne.hashCode());
System.out.println("instanceTwo.hashCode():" + instanceTwo.hashCode());
}
}
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
破解失败
11.反序列化
11.1.1.反序列化非枚举单例
package org.example.jvm01;
import java.io.*;
public class SingletonSerializedTest {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Singleton instanceOne = Singleton.getInstance();
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("filename.ser"));
out.writeObject(instanceOne);
out.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream("filename.ser"));
Singleton instanceTwo = (Singleton) in.readObject();
in.close();
System.out.println("instanceOne hashCode = " + instanceOne.hashCode());
System.out.println("instanceTwo hashCode = " + instanceTwo.hashCode());
}
}
11.1.2.反序列化枚举单例
public enum Singleton implements Serializable {
INSTANCE;
public static void doSomething(){
//do something
}
}
package org.example.jvm01;
import java.io.*;
public class SingletonSerializedTest {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Singleton instanceOne = Singleton.INSTANCE;
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("filename.ser"));
out.writeObject(instanceOne);
out.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream("filename.ser"));
Singleton instanceTwo = (Singleton) in.readObject();
in.close();
System.out.println("instanceOne hashCode = " + instanceOne.hashCode());
System.out.println("instanceTwo hashCode = " + instanceTwo.hashCode());
}
}
instanceOne hashCode = 2133927002
instanceTwo hashCode = 2133927002
破解失败
instanceOne hashCode = 2133927002
instanceTwo hashCode = 2133927002
破解失败