单例模式
提供了一种创建对象的最佳模式
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象
应用:任务管理器、JDK 中 Runtime 类
1.1.1 单例模式的结构
- 单例类:只能创建一个实例的类
- 访问类:使用单例类
1.12 单例模式的实现
分为两种:
- 饿汉式:类加载就会导致该单例对象被创建
- 懒汉式:类加载不会导致该单例对象被创建,而是首次使用该对象时才会创建
-
饿汉式-方式 1 (静态变量成员变量)
public class Singleton { // 1. 创建私有构造方法,外界无法直接方法 private Singleton() { } // 2. 在本类中创建本类对象 private static Singleton instance = new Singleton(); // 3. 提供一个公共的访问方式,让外界获取对象 public static Singleton getInstance() { return instance; } }
-
饿汉式-方式 2(静态代码块)
public class Singleton { // 1. 创建静态构造方法 private Singleton() { } // 2. 声明私有静态 Singleton 类型的变量 private static Singleton instance; // null static { instance = new Singleton(); } // 对外提供公有方法获取对象 public static Singleton getInstance() { return instance; } }
说明:
随着类加载就被创建,有可能一直未被使用,会存在内存浪费的问题 -
枚举方式 -- 方式3
// 枚举方式创建单例模式 public enum Singleton { INSTANCE; }
-
懒汉式 -- 方式1 -- 线程不安全
public class Singleton { // 1. 创建私有构造器 private Singleton() { } // 2. 声明一个对象成员变量 private static Singleton instance; // 3. 提供公有访问对象的方法 public static Singleton getInstance() { if (instance == null) { // 多线程访问的话,会造成线程不安全的问题 instance = new Singleton(); } // instance = new Singleton(); // 不加以判断,每次获取到的对象不同 return instance; }
-
懒汉式 -- 方式2 (添加 synchronized 关键字)
public class Singleton { // 1. 创建私有构造器 private Singleton() { } // 2. 声明一个对象成员变量 private static Singleton instance; // 3. 提供公有访问对象的方法 public static synchronized Singleton getInstance() { if (instance == null) { // 多线程访问的话,会造成线程不安全的问题(添加 synchronized 关键字) instance = new Singleton(); } return instance; }
说明:执行效果很低,只有在初始化 instance 时才会出现线程安全问题,一旦初始化完成就不存在了
-
懒汉式 -- 方式3 (双重检查锁)
没必要让每个线程必须持有锁才能调用该方法,可跳整加锁的时机
// 双重检查锁机制 public class Singleton { private Singleton() { } private static Singleton instance; public static Singleton getInstance() { // 第一次判断,如果 instance 不为 null,不进入抢锁阶段,直接返回实例 if (instance == null) { synchronized (Singleton.class) { // 抢到锁之后再次判断是否为 null if (instance == null) { instance = new Singleton(); } } } return instance; } }
说明:在多线程的情况下,可能会出现空指针问题,出现问题的原因是 JVM 在实例化对象的时候会进行优化和指令重排序操作
解决方法:使用 volatile 关键字,保证可见性和有序性
-
懒汉式 --方式4 -- 优化
public class Singleton { private Singleton() { } private static volatile Singleton instance; public static Singleton getInstance() { // 第一次判断,如果 instance 不为 null,不进入抢锁阶段,直接返回实例 if (instance == null) { synchronized (Singleton.class) { // 抢到锁之后再次判断是否为 null if (instance == null) { instance = new Singleton(); } } } return instance; } }
说明:添加 volatile 关键字,能够保证在多线程的情况下线程安全也不会有性能问题
-
懒汉式 -- 方式5 -- 静态内部类
public class Singleton { private Singleton() { } // 定义一个静态内部类 private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } // 对外提供静态方法获取该对象 public static Singleton getInstance() { return SingletonHolder.INSTANCE; } }
说明:第一次去加载 Singleton 时不会去初始化 INSTANCE, 只有第一次调用 getInstance,虚拟机加载 SingletonHolder 并初始化 INSTANCE,这样不仅能保证线程安全,也能保证 Singleton 类的一致性
小结:
静态内部类单例模式时一种优秀的单例模式,是开源项目种比较常用的一种单例模式,在没有加锁的情况下,保证了多线程的安全,并且没有任何性能影响和空间的浪费
1.14 存在的问题
破环单例模式:
使上面定义的单例类(Singleton)可以创建多个对象,枚举方式除外。有两种方式,分别是序列化和反射
- 序列化反序列化
// 破坏单例模式,序列化反序列化
public class Client {
public static void main(String[] args) throws Exception {
writeObjectFile();
// 返回的两个对象不一致
readObjectFromFile();
readObjectFromFile();
}
public static void readObjectFromFile() throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("../\\a.txt"));
Singleton instance = (Singleton) ois.readObject();
System.out.println(instance);
ois.close();
}
public static void writeObjectFile() throws Exception {
Singleton instance = Singleton.getInstance();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("../\\a.txt"));
oos.writeObject(instance);
oos.close();
}
}
- 反射
// 反射破环单例模式
public class ClientTest {
public static void main(String[] args) throws Exception {
// 1. 获取 Singleton 的字节码对象
Class<Singleton> singletonClass = Singleton.class;
// 2. 获取无参构造方法对象
Constructor<Singleton> cons = singletonClass.getDeclaredConstructor();
// 3. 取消访问修饰检查
cons.setAccessible(true);
// 4. 创建 Singleton 对象
Singleton instance = cons.newInstance();
Singleton instance1 = cons.newInstance();
System.out.println(instance == instance1); // false
}
}
1.15 解决方法
-
序列化、反序列化解决方法
在 Singleton 类里面添加 readResolve() 方法,在反序列化时被调用,如果定义了这个方法,就返回这个方法的值,如果没有定义,则返回新 new 出来的对象
-
反射解决方法
在构造方法中添加判断对象是否为 null,若不是则抛出 RuntimeException 异常