目录

单例模式

李羽秋
李羽秋 2022年06月20日  ·  阅读 1,450

单例模式

单例模式是java中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象方式,可以直接访问,不需要实例化该类的对象。

1.单例模式的实现

单例设计模式分为两种:

  • 饿汉式: 类加载就会导致该单实例对象被创建
  • 懒汉式: 类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建

饿汉式-静态变量方式

/***
 * 饿汉式01-静态变量方式
 */
public class EHanOne {
    //私有构造方法
    private EHanOne() {}

    // 在成员位置创建该类的对象
    private static EHanOne instance = new EHanOne();

    //对外提供静态方法获取该对象
    public static EHanOne getInstance() {
        return instance;
    }

}

说明:该方式在成员位置声明EHanOne类型的静态变量,并创建EHanOne类的对象instance。instance对象是随着类的加载而创建的。如果该对象足够大的话,而一直没有使用就会造成内存的浪费

饿汉式-静态代码块方式

/***
 * 饿汉式-在静态代码块中创建该类对象
 */
public class EHanTwo {

    //私有构造方法
    private EHanTwo() {}

    //在成员位置创建该类的对象
    private static EHanTwo instance;

    static {
        instance = new EHanTwo();
    }

    //对外提供静态方法获取该对象
    public static EHanTwo getInstance() {
        return  instance;
    }

}

说明:该方式在成员位置声明EHanTwo类型的静态变量,而对象的创建是在静态代码块中,也是对着类的加载而创建。所以和饿汉式的方法1基本上一样,当然也存在内存浪费的问题。

懒汉式-线程不安全

/***
 * 懒汉式-线程不安全
 */
public class LanHanOne {
    //私有构造方法
    private LanHanOne() {}

    //对外提供静态方法获取
    private static LanHanOne instance;

    //对外提供静态方法获取该对象
    public static LanHanOne getInstance() {

        if(instance == null) {
            instance = new LanHanOne();
        }
        return instance;
    }
}

说明:从上面代码中我们可以看出该方式在成员位置声明LanHanOne类型的静态变量,并没有进行对象的赋值操作,那么什么时候赋值的呢?当调用getInstance()方法获取Singleton类的对象的时候猜创建LanHanOne类的对象,这样就实现了懒加载的效果。但是,如果是多线程的环境,会出现线程安全问题。

懒汉式-线程安全

/***
 * 懒汉式-线程安全
 */
public class LanHanTwo {

    //私有构造方法
    private LanHanTwo() {}

    //在成员位置创建该类对象
    private static LanHanTwo instance;

    //对外提供静态方法
    public static synchronized LanHanTwo getInstance() {
        if (instance == null) {
            instance = new LanHanTwo();
        }
        return  instance;
    }
}

说明:该方式也实现类懒加载效果,同时解决了线程安全问题。但是在getInstance()方法上添加了synchronized关键字,导致该方法的执行效果特别低。从上面代码我们可以看出,其实就是在初始化instance的时候才会出现线程安全问题,一旦初始化完成就不存在了。

懒汉式-双重检查锁

再来讨论一下懒汉模式中加锁的问题,对于getInstance()方法来说,绝大部分的操作都是读操作,读操作是线程安全的,所以我们没必要让每个线程必须持有锁才能调用该方法,我们需要调整加锁的时机。由此也产生了一种新的实现模式:双重检查锁模式。

/***
 * 懒汉式-双重检查锁
 */
public class LanHanThree {
    // 私有构造方法
    private LanHanThree() {}

    private static LanHanThree instance;

    public static LanHanThree getInstance() {
        // 第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实例
        if(instance == null) {
            synchronized (LanHanThree.class) {
                //抢到锁之后再次判断是否为null
                if (instance == null){
                    instance = new LanHanThree();
                }
            }
        }
        return instance;
    }
}

双重检查锁模式是一种非常好的单例实现模式,解决了单例、性能、线程安全问题,上面的双重检测模式看上去完美无缺,其实是存在问题,在多线程的情况下,可能会出现空指针问题,出现问题的原因是JVM在实例化对象的时候会进行优化和指令重排序操作。

要解决双重检查锁模式带来空指针异常的问题,只需要使用volatile关键字,volatile关键字可以保证可见性和有序性。

/***
 * 双重检查方式
 */
public class LanHanFour {
    //私有构造方法
    private LanHanFour() {}

    private static volatile LanHanFour instance;

    //对外提供静态方法获取该对象
    public static LanHanFour getInstance() {
        //第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实际
        if (instance == null) {
            synchronized (LanHanFour.class) {
                //抢到锁之后再次判断是否为空
                if (instance == null) {
                    instance = new LanHanFour();
                }
            }
        }
        return instance;
    }
}

小结:添加volatile关键字之后的双重检查锁模式是一种比较好的单例实现模式,能够保证在多线程的情况下,线程安全也不会有性能问题。

懒汉式-静态内部类方式

静态内部类单例模式中实例由内部类创建,由于JVM在加载外部类的过程中,是不会加载静态内部类,只有内部类的属性/方法调用时才会被加载,并初始化其静态属性。静态属性由于被static修饰,保证只被实例化一次,并且严格保证实例化顺序。

public class LanHanFive {
    //私有构造方法
    private LanHanFive(){}
    
    private static class SingletonHolder{
        private static final LanHanFive INSTANCE = new LanHanFive();
    }
    
    //对外提供静态方法获取该对象
    public static LanHanFive getInstance() {
        return SingletonHolder.INSTANCE;
    }

}

说明: 第一次加载LanHanFive类时不会去初始化INSTANCE,只有第一次调用getInstance,虚拟机加载SingletonHolder,并初始化INSTANCE,这样不仅能确保线程安全,也能保证Singleton类的唯一性。

2.存在的问题

2.1 案例1-序列化与反序列化

SingletonOne类

public class SingletonOne implements Serializable {
    //私有构造方法
    private SingletonOne() {}

    private static class SingletonHolder {
        private static final SingletonOne INSTANCE = new SingletonOne();
    }

    //对外提供静态方法获取该对象
    public static SingletonOne getInstance(){
        return SingletonHolder.INSTANCE;
    }
}

Test类

public class TestOne {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
//        writeObject2File();
        SingletonOne s1 = readObjectFromFile();
        SingletonOne s2 = readObjectFromFile();
        //判断两个反序列化的对象是否是同一个对象
        System.out.println(s1 == s2);
    }
    private static SingletonOne readObjectFromFile() throws IOException, ClassNotFoundException {
        //创建对象输入流对象
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\1.txt"));
        //第一个读取Singleton对象
        SingletonOne instance = (SingletonOne) ois.readObject();

        return instance;

    }
    public static void writeObject2File() throws IOException {
        //获取SingletonOne对象
        SingletonOne instance = SingletonOne.getInstance();
        //创建对象输出流
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\1.txt"));
        //将instance对象写出文件中
        oos.writeObject(instance);

    }
}

结果

image-20220620141028817

运行结果为false,说明序列化与反序列化已经破坏了单例设计模式。

2.2 案列2-反射

singletonTwo类

public class SingletonTwo {
    //私有构造方法
    private SingletonTwo(){}

    private static volatile SingletonTwo instance;

    //对外提供静态方法获取该对象
    public static SingletonTwo getInstance() {
        if (instance !=null) {
            return  instance;
        }

        synchronized (SingletonTwo.class) {
            if (instance != null) {
                return instance;
            }
            instance = new SingletonTwo();
            return instance;
        }
    }

}

Test类

public class TestTwo {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        //获取Singleton类的字节码对象
        Class<SingletonTwo> clazz = SingletonTwo.class;
        //获取Singleton类的私有无参构造方法对象
        Constructor<SingletonTwo> constructor = clazz.getDeclaredConstructor();
        //取消访问检查
        constructor.setAccessible(true);

        //创建Singleton类的对象s1
        SingletonTwo s1 = constructor.newInstance();
        SingletonTwo s2 = constructor.newInstance();

        //判断通过反射创建两个singleton对象是否是同一个对象
        System.out.println(s1 == s2);

    }

}

结果

image-20220620143032968

上面代码运行结果是false,表明反射已经打破了单例设计模式。

分类: 设计模式
标签: