设计模式之单例模式
1. 概括
如果你希望一个类从始至终,都只能创建一个全局对象,那么应该使用单例模式
2. 结构
- 写一个类,提供一个获取自己实例的方法
- 私有化构造方法
3. 实现
单例模式有饿汉式和懒汉式两种
3.1 饿汉式
- 是指在类加载时就去创建单例对象
- java有两种方式,成员变量声明式、静态代码块式
- 成员变量声明式
public class Singleton {
// 构造私有化
private Singleton() {
}
// 单例对象
private static Singleton instance = new Singleton();
// 提供获取方法
public static Singleton getInstance() {
return instance;
}
}
- 静态代码块式
public class Singleton {
// 构造私有化
private Singleton() {
}
// 静态代码块
private static Singleton instance;
static {
instance = new Singleton();
}
// 提供获取方法
public static Singleton getInstance() {
return instance;
}
}
3.2 懒汉式
- 只有在获取对象时,对象才会被创建
public class Singleton {
// 构造私有化
private Singleton() {
}
private static Singleton instance;
public static Singleton getInstance() {
// 如果是第一次获取实例,才会创建
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
3.2.1 注意
这样会有一个问题,如果有多线程条件下,线程1经过if
判断后,在创建对象之前,线程2抢到了线程,那么线程1和2就会创建两个不同的对象,这样就破坏了单例模式
3.2.2 问题解决
- 同步锁
- 在获取对象实例的方法上
synchronized
关键字 - 但是这样,程序的效率就会变低,因为线程问题只会在第一次创建实例时才会出现问题,而
synchronized
关键字每次都会加锁
- 在获取对象实例的方法上
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
- 双重检查锁(同步代码块)
- 双重检查锁模式带来空指针异常的问题,只需要使用
volatile
关键字,volatile
关键字可以保证可见性和有序性。
- 双重检查锁模式带来空指针异常的问题,只需要使用
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;
}
- 静态内部类式(推荐)
- JVM在加载类过程中,不会加载静态内部类,只有内部类的属性和方法被调用时才会被加载,利用这个特性,把要实例的对象在静态内部类中声明,用
static final
修饰,保证被实例一次
- JVM在加载类过程中,不会加载静态内部类,只有内部类的属性和方法被调用时才会被加载,利用这个特性,把要实例的对象在静态内部类中声明,用
public class Singleton {
// 构造私有化
private Singleton() {
}
// 静态内部类
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
- 枚举
- 枚举线程安全,并且只会装载一次写法简单
- 枚举是饿汉式
public enum Singleton {
INSTANCE;
}
public class Client {
public static void main(String[] args) {
Singleton instance1 = Singleton.INSTANCE;
Singleton instance2 = Singleton.INSTANCE;
System.out.println(instance1 == instance2); // true
}
}
4. 单例破坏
- 虽说私有的构造方法,但利用java的特性仍可以轻松破坏单例(枚举除外),
- 一般有两种方式:序列化、反射
4.1 序列化
- 破坏演示:反序列化两次
public class Client {
public static void main(String[] args) throws Exception{
Singleton instance = Singleton.getInstance();
// 序列化
// writeObject(instance);
// 反序列化两次
Singleton singleton1 = readObject();
Singleton singleton2 = readObject();
System.out.println(singleton1 == singleton2); // false
}
public static void writeObject(Singleton singleton) throws Exception{
ObjectOutputStream outputStream
= new ObjectOutputStream(new FileOutputStream("C:\\Users\\admin\\Desktop\\新建文件夹\\aaa"));
// 序列化
outputStream.writeObject(singleton);
outputStream.flush();
outputStream.close();
}
public static Singleton readObject() throws Exception {
ObjectInputStream inputStream
= new ObjectInputStream(new FileInputStream("C:\\Users\\admin\\Desktop\\新建文件夹\\aaa"));
return (Singleton) inputStream.readObject();
}
}
- 解决:在单例类中提供一个
Object readResolve()
的方法,返回单例对象,这和jdk
源码有关
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;
}
/**
* 解决 序列化 破坏单例
* @return
*/
private Object readResolve() {
return SingletonHolder.INSTANCE;
}
}
4.2 反射
- 破坏演示:反射构造方法,用构造对象创建两次实例
public class Client {
public static void main(String[] args) throws Exception{
// 获取单例类的class对象
Class<Singleton> clazz = Singleton.class;
// 获取构造器
Constructor<Singleton> constructor = clazz.getDeclaredConstructor();
// 关闭安全检查
constructor.setAccessible(true); // 暴力反射
// 创建两次对象
Singleton singleton1 = constructor.newInstance();
Singleton singleton2 = constructor.newInstance();
System.out.println(singleton1 == singleton2); // false
}
}
- 解决:在构造创建对象时,如果不是第一次创建对象,报一个异常,停止程序运行
public class Singleton implements Serializable {
private static boolean flag = false;
// 构造私有化
private Singleton() {
// 第一次是false
if (flag) {
throw new RuntimeException("不可创建多实例");
}
// 至为true,防止第二次进入
flag = true;
}
// 省略....
}
4.3 绝杀,无解
但是、但是、但是:反射仍然是可以反射private static boolean flag
的
public class Client {
public static void main(String[] args) throws Exception{
Class<Singleton> clazz = Singleton.class;
Constructor<Singleton> constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true); // 暴力反射
// 第一次创建对象
Singleton singleton1 = constructor.newInstance();
// 获取flag的Filed对象
Field flag = clazz.getDeclaredField("flag");
flag.setAccessible(true);
flag.setBoolean(singleton1, false); // 设为false
// 第二次创建对象
Singleton singleton2 = constructor.newInstance();
System.out.println(singleton1 == singleton2); // false
}
}
所以:反射破坏单例性解决办法:绝杀,无解!!!!!(除非使用枚举)