java中聊聊单例模式 - 设计模式(二)

什么是单例模式

保证系统运行中,应⽤该模式的类只有⼀个对象实例
全局唯一

使⽤场景

业务系统全局只需要⼀个对象实例,⽐如发号器
redis连接对象
Spring IOC容器中的bean默认就是单例
spring @autowire的依赖注⼊对象默认都是单例的

如何写一个单例模式

1.将构造函数私有化
2.通过静态方法获取一个唯一实例
3.保证线程安全
4.防止反序列化造成的新实例等

饿汉式

JDK中Runtime类 饿汉⽅式
在这里插入图片描述

// 模仿着Runtime实现
/**
 * 饿汉 : 声明静态时已经初始化,在获取对象之前就初始化
 */
public class SingleHungerPattern {

	//  3.声明静态时已经初始化,在获取对象之前就初始化,没有线程安全问题
    private static SingleHungerPattern instance = new SingleHungerPattern();
    
	// 2.通过静态方法获取一个唯一实例
    public static SingleHungerPattern getInstance(){
        return instance;
    }
	// 1.私有构造函数
    private SingleHungerPattern(){
        System.out.println("SingleHungerPattern实例化");
    }

    public static void main(String[] args) {
        SingleHungerPattern instance1 = SingleHungerPattern.getInstance();
        SingleHungerPattern instance2 = SingleHungerPattern.getInstance();
        System.out.println(instance1);
        System.out.println(instance2);
    }
}

在这里插入图片描述

懒汉式

DCL 双重检查锁

/**
 * 懒汉 就是所谓的懒加载,延迟创建对象
 * 最多使用 : DCL 双重检查锁
 */
public class SingleLazyPattern {

    /**
     * 3. 使用 volatile 保证线程安全
     * 禁止指令重排
     * 保证可见性
     * 不保证原子性
     */
    private static volatile SingleLazyPattern instance;
    // 1.私有构造函数
    private SingleLazyPattern(){
        System.out.println("SingleLazyPattern实例化了");
    }

    /**
     *  2.通过静态方法获取一个唯一实例
     *  DCL 双重检查锁定 (Double-CheckedLocking)
     *  在多线程情况下保持⾼性能
     */
    public static SingleLazyPattern getInstance(){
        if(instance == null){
            synchronized (SingleLazyPattern.class){
                if(instance == null){
                // 1. 分配内存空间 2、执行构造方法,初始化对象 3、把这个对象指向这个空间
                    instance = new SingleLazyPattern();
                }
            }
        }
        return instance;
    }


    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            new Thread(()->{
                SingleLazyPattern.getInstance();
            }).start();
        }
    }

}

静态内部类

/**
 * 静态内部类
 */
public class StaticInnerClass {

    // 1.私有构造函数
    private StaticInnerClass(){
        System.out.println("StaticInnerClass实例化");
    }

    // 2.通过静态方法获取一个唯一实例
    public static StaticInnerClass getInstance(){
        return innerClass.instance;
    }

    // 3.保证线程安全  调用 StaticInnerClass.instance 的时候,才会对单例进行初始化
    private static class innerClass{
        private final static StaticInnerClass instance = new StaticInnerClass();
    }

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            new Thread(()->{
                 StaticInnerClass.getInstance();
            }).start();
        }
    }

}

使用枚举

枚举也是类,并且还继承了java.lang.Enum。
由汇编可以看出,枚举的初始化过程是在静态代码块中进行,
我们知道,静态代码块只在类首次加载的时候执行一次,final域保证引用地址不会改变。
同时,我们也没有看到有公有的构造方法,客户端也就不能创建新的实例,
由此可见,枚举类型具有实例受限的特性。
解决单例的并发问题,主要解决的就是初始化过程中的线程安全问题。
所以,由于枚举的以上特性,枚举实现的单例是天生线程安全的。
/**
 * 使用枚举获取单例
 */

public enum SingleEnum {

    Instance;

    SingleEnum(){
        System.out.println("SingleEnum实例化了");
    }

    public static SingleEnum getInstance(){
        return Instance;
    }

    public static void main(String[] args) {
        SingleEnum instance1 = SingleEnum.getInstance();
        SingleEnum instance2 = SingleEnum.getInstance();
        System.out.println(instance1);
        System.out.println(instance2);
    }

}

java中有几种可以创建对象的方式

1、通过new语句实例化一个对象
2、通过反射机制
3、通过clone()创建对象
4、通过反序列化的方式创建对象

思考 那么以上的四种创建对象的方式能不能破坏单例模式呢

 构造器私有已经无法通过new 创建对象了

序列化会破坏单例模式

链接: 单例、序列化和readResolve()方法.
链接: Java 之 Serializable 序列化和反序列化的概念,作用的通俗易懂的解释.

import java.io.*;

public class SingleHungerPattern implements Serializable {

    //  3.声明静态时已经初始化,在获取对象之前就初始化,没有线程安全问题
    private static SingleHungerPattern instance = new SingleHungerPattern();

    // 2.通过静态方法获取一个唯一实例
    public static SingleHungerPattern getInstance(){
        return instance;
    }
    // 1.私有构造函数
    private SingleHungerPattern(){
        System.out.println("SingleHungerPattern实例化");
    }

    public static void main(String[] args) throws Exception {
        SingleHungerPattern instance1 = SingleHungerPattern.getInstance();
        System.out.println(instance1);
        serialize(instance1);
        SingleHungerPattern instance2 = deserialize();
        System.out.println(instance2);
    }

    /**
     * 序列化
     */
    private static void serialize(SingleHungerPattern instance) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("d:/flyPig.txt")));
        oos.writeObject(instance);
        oos.close();
        System.out.println("对象序列化成功!");
    }

    /**
     * 反序列化
     */
    private static SingleHungerPattern deserialize() throws Exception {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("d:/flyPig.txt")));
        SingleHungerPattern instance = (SingleHungerPattern) ois.readObject();
        System.out.println("对象反序列化成功!");
        return instance;
    }
    

}

在这里插入图片描述

如何防止序列化破坏单例模式

    /**
    *若目标类有readResolve方法,那就通过反射的方式调用要被反序列化的类中的readResolve方法,返回一个
    *对象,然后把这个新的对象复制给之前创建的obj(即最终返回的对象)。那被反序列化的类中的
    * readResolve 方法里是什么?就是直接返回我们的单例对象。
    * 防止序列化造成的破坏
    * @return
    */
    private Object readResolve() {
        return instance;
    }

克隆也会破坏单例模式

public class SingleHungerPattern implements Cloneable{

    //  3.声明静态时已经初始化,在获取对象之前就初始化,没有线程安全问题
    private static SingleHungerPattern instance = new SingleHungerPattern();

    // 2.通过静态方法获取一个唯一实例
    public static SingleHungerPattern getInstance(){
        return instance;
    }
    // 1.私有构造函数
    private SingleHungerPattern(){
        System.out.println("SingleHungerPattern实例化");
    }

    public static void main(String[] args) throws Exception {
        SingleHungerPattern instance1 = SingleHungerPattern.getInstance();
        System.out.println(instance1);
        SingleHungerPattern instance2 = (SingleHungerPattern)instance1.clone();
        System.out.println(instance2);
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();//单例实现了克隆接口 可以利用反射破坏单例
        //return instance;//解决办法 即使通过反射调用clone方法 获取到的依然是原来的实例对象
    }
    
}

分别对以上四种方式进行反射

饿汉- 被破坏

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class Test {
    public static void main(String[] args) throws NoSuchMethodException,
            IllegalAccessException, InvocationTargetException, InstantiationException {
        SingleHungerPattern instance1 = SingleHungerPattern.getInstance();
        Constructor<SingleHungerPattern> declaredConstructor = SingleHungerPattern.class.
                getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        SingleHungerPattern instance2 = declaredConstructor.newInstance();
        System.out.println(instance1);
        System.out.println(instance2);
    }
}

DCL 双重检查锁- 被破坏

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class Test {

    public static void main(String[] args) throws NoSuchMethodException,
            IllegalAccessException, InvocationTargetException, InstantiationException {
        SingleLazyPattern instance1 = SingleLazyPattern.getInstance();
        Constructor<SingleLazyPattern> declaredConstructor = SingleLazyPattern.class.
                getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        SingleLazyPattern instance2 = declaredConstructor.newInstance();
        System.out.println(instance1);
        System.out.println(instance2);
    }
}

静态内部类- 被破坏

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class Test {

    public static void main(String[] args) throws NoSuchMethodException,
            IllegalAccessException, InvocationTargetException, InstantiationException {
        StaticInnerClass instance1 = StaticInnerClass.getInstance();
        Constructor<StaticInnerClass> declaredConstructor = StaticInnerClass.class.
                getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        StaticInnerClass instance2 = declaredConstructor.newInstance();
        System.out.println(instance1);
        System.out.println(instance2);
    }

}

枚举 - 不会破坏


import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class Test {
    public static void main(String[] args) throws NoSuchMethodException,
            IllegalAccessException, InvocationTargetException, InstantiationException {
        SingleEnum instance1 = SingleEnum.getInstance();
        Constructor<SingleEnum> declaredConstructor = SingleEnum.class.
                getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        SingleEnum instance2 = declaredConstructor.newInstance();
        System.out.println(instance1);
        System.out.println(instance2);
    }
}

Effective java 书中

链接: 为什么要用枚举实现单例模式(避免反射、序列化问题).

89条:对于实例控制,枚举类型优先于readResolve
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值