1. 单例模式
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
单例设计模式分类有两种:
- 饿汉式:类加载就会导致该单实例对象被创建
- 懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建
单例模式使用的场景:需要频繁的进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session 工厂等)
1.1. 饿汉式-静态常量方式(保证实例一定用到的情况下可以使用)
public class Singleton {
//1.私有构造器,不提供给外界使用
private Singleton() {
}
//2.通过静态常量使用构造器
private final static Singleton instance = new Singleton();
//提供外界获取实例对象
public static Singleton getInstance() {
return instance;
}
}
1.2. 饿汉式-静态代码块方式(保证实例一定用到的情况下可以使用)
public class Singleton {
private Singleton() {
}
private static Singleton instance;
static {
instance = new Singleton();
}
public static Singleton getInstance() {
return instance;
}
}
1.3. 懒汉式-线程不安全(不推荐使用)
public class Singleton {
private Singleton() {
}
private static Singleton instance;
public static Singleton getInstance() {
//保证只有一个实例对象
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
1.4. 懒汉式-线程安全-同步方法(不推荐使用)
public class Singleton {
private Singleton() {
}
private static Singleton instance;
//加上synchronized同步关键字保证线程安全
public static synchronized Singleton getInstance() {
//保证只有一个实例对象
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
1.5. 懒汉式-线程安全-同步代码块(不推荐使用)
public class Singleton {
private Singleton() {
}
private static Singleton instance;
//加上synchronized同步关键字保证线程安全
public static synchronized Singleton getInstance() {
//保证只有一个实例对象
if (instance == null) {
//同步代码块
synchronized(Singleton.class){
instance = new Singleton();
}
}
return instance;
}
}
1.6. 懒汉式-双重检查锁模式(推荐使用)
public class Singleton {
private Singleton() {
}
private static volatile Singleton instance;
// 加上synchronized同步关键字保证线程安全
public static Singleton getInstance() {
// 判断是否加锁,写操作加锁,读操作不加锁
if (instance == null) {
synchronized (Singleton.class) {
instance = new Singleton();
}
}
return instance;
}
}
双重检查锁模式是一种非常好的单例实现模式,解决了单例、性能、线程安全问题,上面的双重检测锁模式看上去完美无缺,其实是存在问题,在多线程的情况下,可能会出现空指针问题,出现问题的原因是JVM在实例化对象的时候会进行优化和指令重排序操作。
解决双重检查锁模式带来空指针异常的问题,只需要使用 volatile
关键字, volatile
关键字可以保证可见性和有序性。
1.7. 懒汉式-静态内部类(推荐使用)
public class Singleton {
private Singleton() {
}
// 定义一个静态内部类
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
1.8. 枚举方式(饿汉式)(推荐使用)
public enum SingletonEnum {
INSTANCE;
private String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public static void main(String[] args) {
SingletonEnum instance = SingletonEnum.INSTANCE;
instance.setValue("Hello, World!");
System.out.println(instance.getValue());
}
枚举类实现单例模式是唯一一种不会被破坏的单例实现模式
存在的问题
通过序列化反序列化破坏单例模式
public class Singleton implements Serializable {
private Singleton() {
}
// 定义一个静态内部类
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
public class Client {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// writeObject2File();
readObjectFromFile();
readObjectFromFile();
}
// 从文件读取数据
public static void readObjectFromFile() throws IOException, ClassNotFoundException {
// 创建对象输入流对象
ObjectInputStream ois = new ObjectInputStream(Files.newInputStream(Paths.get("C:\\Users\\Better\\Desktop\\test.txt")));
//读对象
Singleton instance = (Singleton)ois.readObject();
System.out.println(instance);
// 释放资源
ois.close();
}
// 向文件中写数据
public static void writeObject2File() throws IOException {
Singleton instance = Singleton.getInstance();
// 创建对象输出流对象
ObjectOutputStream oos = new ObjectOutputStream(Files.newOutputStream(Paths.get("C:\\Users\\Better\\Desktop\\test.txt")));
// 写对象
oos.writeObject(instance);
// 关闭资源
oos.close();
}
}
结果:存在两个实例代表单例模式被破坏
通过反射破坏单例模式
public class Singleton {
// 私有构造方法
private Singleton() {
}
private static volatile Singleton instance;
// 对外提供静态方法获取该对象
public static Singleton getInstance() {
if (instance != null) {
return instance;
}
synchronized (Singleton.class) {
if (instance != null) {
return instance;
}
instance = new Singleton();
return instance;
}
}
}
public class Test {
public static void main(String[] args) throws Exception {
// 获取Singleton类的字节码对象
Class<Singleton> clazz = Singleton.class;
// 获取Singleton类的私有无参构造方法对象
Constructor<Singleton> constructor = clazz.getDeclaredConstructor();
// 取消访问检查
constructor.setAccessible(true);
// 创建Singleton类的对象s1
Singleton s1 = constructor.newInstance();
// 创建Singleton类的对象s2
Singleton s2 = constructor.newInstance();
// 判断通过反射创建的两个Singleton对象是否是同一个对象
System.out.println(s1 == s2);
}
}
输出结果为false
解决问题
解决序列化反序列化破坏单例模式的方法
在Singleton类中添加readdResolve方法,在反序列化时被反射调用,如果定义了这个方法,就返回这个方法的值,如果没有定义,则返回新new出来的对象。
public class Singleton implements Serializable {
private Singleton() {
}
// 定义一个静态内部类
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
//!!!当进行反序列化时,会自动调用该方法,将该方法的返回值直接返回
public Object readResolve() {
return SingletonHolder.INSTANCE;
}
}
源码解析
public final Object readObject() throws IOException, ClassNotFoundException{
...
// if nested read, passHandle contains handle of enclosing object
int outerHandle = passHandle;
try {
Object obj = readObject0(false);//重点查看readObject0方法
.....
}
private Object readObject0(boolean unshared) throws IOException {
...
try {
switch (tc) {
...
case TC_OBJECT:
return checkResolve(readOrdinaryObject(unshared));//重点查看readOrdinaryObject方法
...
}
} finally {
depth--;
bin.setBlockDataMode(oldMode);
}
}
private Object readOrdinaryObject(boolean unshared) throws IOException {
...
//isInstantiable 返回true,执行 desc.newInstance(),通过反射创建新的单例类,
obj = desc.isInstantiable() ? desc.newInstance() : null;
...
// 在Singleton类中添加 readResolve 方法后 desc.hasReadResolveMethod() 方法执行结果为true
if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()) {
// 通过反射调用 Singleton 类中的 readResolve 方法,将返回值赋值给rep变量
// 这样多次调用ObjectInputStream类中的readObject方法,继而就会调用我们定义的readResolve方法,所以返回的是同一个对象。
Object rep = desc.invokeReadResolve(obj);
...
}
return obj;
}
解决反射破坏单例模式的方法
public class Singleton {
// 私有构造方法
private Singleton() {
// 反射破解单例模式需要添加的代码
if (instance != null) {
throw new RuntimeException("已经创建对象,不能再创建");
}
}
private static volatile Singleton instance;
// 对外提供静态方法获取该对象
public static Singleton getInstance() {
if (instance != null) {
return instance;
}
synchronized (Singleton.class) {
if (instance != null) {
return instance;
}
instance = new Singleton();
return instance;
}
}
}
JDK中的案例-Runtime类
public class Runtime {
private static Runtime currentRuntime = new Runtime();
public static Runtime getRuntime() {
return currentRuntime;
}
private Runtime() {}
//其它代码省略
......
}
可以看出Runtime类使用了单例模式的饿汉式-静态成员变量方式