设计模式-单例模式

本文介绍了单例模式的概念和优点,详细讲解了饿汉式和懒汉式的实现,包括它们的线程安全问题及解决方案。讨论了双重检测锁、静态内部类这两种优化的单例实现方式,以及如何通过反射和反序列化破坏单例。最后,提出了使用枚举实现单例作为最安全的方式。
摘要由CSDN通过智能技术生成

设计模式-单例模式

1.概念:
1)单例模式为系统中该类有且只有一个实例,该类自行实例化并向提供公共访问方法。
2)优点:无需频繁的创建销毁对象,节约系统资源。

2.类别。
1)饿汉式

class EagerSingleton {
    // 静态变量,类在创建之初就会执行实例化动作。
    private static EagerSingleton instance = new EagerSingleton();

    // 私有化构造函数,使外界无法创建实例
    private EagerSingleton() {
    }

    // 为外界提供获取实例接口
    public static EagerSingleton getInstance() {
        return instance;
    }
}
  • 上面是饿汉式单例模式的标准代码,EagerSingleton类的实例因为变量instance申明为static的关系,在类加载过程中便会执行。
  • 由此带来的好处是Java的类加载机制本身为我们保证了实例化过程的线程安全性。
  • 缺点是这种空间换时间的方式,即使类实例本身还未用到,实例也会被创建。

2)懒汉式
a) 普通懒汉式

public class LazySingleton {
    private static LazySingleton instance = null;

    private LazySingleton() {
        System.out.println(Thread.currentThread().getName() + "ok");
    }

    // 为外界提供获取实例接口
    public static LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton(); // 懒加载
        }
        return instance;
    }
}
  • 普通懒汉式的缺点,涉及到并发编程的原子性。
  • 实例中,创建实例的代码逻辑失去了原子性从而导致可能存在多个实例创建的情况。
    /**
     * 多线程并发
     * 发生了多次实例化对象
     */
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                LazySingleton.getInstance();
            }).start();
        }
    }

在这里插入图片描述

b) 双重检测锁

/**
 * 双重检测锁
 */
class Singleton2 implements Serializable {

    //volatile 原子性操作
    private volatile static Singleton2 instance = null;

    /**
     * 设置标志位
     */
    private static boolean youyuan = false;

    private Singleton2() {
        synchronized (Singleton2.class) {
            if (youyuan == false) {
                youyuan = true;
            } else {
                throw new RuntimeException("不要试图使用反射破坏异常");
            }
        }
        System.out.println(Thread.currentThread().getName() + "  ok");
    }

    public static Singleton2 getInstance() {
        //先检查实例是否存在,如果不存在才进入下面的同步块
        if (instance == null) {
            //同步块,线程安全的创建实例
            synchronized (Singleton2.class) {
                //再次检查实例是否存在,如果不存在才真正的创建实例
                if (instance == null) {
                    instance = new Singleton2();//不是一个原子性操作
                }
            }
        }
        return instance;
    }
 }

双重检测 if (instance == null) 是为了防止高并发下出现问题:
假设有两个线程A,B同时通过了第一个if判断,A先获取到锁,B就会原地等待A的锁释放,A生成了实例之后释放锁,此时B就会获取锁也去生成一个实例。而我们加了第二个if判断之后,B获得了锁之后发现实例已经生成了就不会继续创建。

volatile 修饰实例主要是禁止重排,具体了解可以看一下我的这本篇文章
volatile关键字的作用

  /**
     * 线程安全
     */
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                Singleton2.getInstance();
            }).start();
        }
    }

在这里插入图片描述

这样虽然解决了原子性和有序性的问题还是存在漏洞,,我们可以通过反射或者反序列化去进行破坏。

反射破坏

public static void main(String[] args) throws Exception {
        //可以通过反射破坏
        Constructor<Singleton2> declaredConstructor = Singleton2.class.getDeclaredConstructor();
        Field youyuan = Singleton2.class.getDeclaredField("youyuan");
        youyuan.setAccessible(true);
        declaredConstructor.setAccessible(true);

        Singleton2 singleton2 = declaredConstructor.newInstance();
        //修改字段
        youyuan.set(singleton2, false);
        Singleton2 singleton21 = declaredConstructor.newInstance();
        System.out.println(singleton21);
        System.out.println(singleton2);
    }

在这里插入图片描述

反序列化破坏

   public static void main(String[] args) throws Exception {
        try {
            // 可以通过反序列化破坏
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test"));
            oos.writeObject(Singleton2.getInstance());
            oos.close();

            File file = new File("test");
            ObjectInputStream ins = new ObjectInputStream(new FileInputStream(file));
            Singleton2 singleton22 = (Singleton2) ins.readObject();
            System.out.println(singleton22 == Singleton2.getInstance());
            file.delete();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

在这里插入图片描述

c)静态内部类

class Singleton3 {
    private Singleton3() {
        System.out.println(Thread.currentThread().getName() + "ok");
    }

    // 只有当类被调用时,才会加载
    private static class SingletonHolder {
        // 静态初始化器,由JVM来保证线程安全
        private static Singleton3 instance = new Singleton3();
    }

    public static Singleton3 getInstance() {
        return SingletonHolder.instance;
    }

}

使用类加载机制去创建实例类似双重检测锁,同样是解决了原子性和有序性的问题,,但还是可以通过反射或者反序列化去进行破坏。比起双重检测锁更加简洁。

3)枚举

enum Singleton {
    uniqueInstance;

    public Singleton singletonOperation() {
        // 单例类的其它操作
        return uniqueInstance;
    }
 }

最安全的方式,解决了原子性和有序性的问题,也不会被反射或者反序列化去破坏。

反射破坏

public static void main(String[] args) throws Exception {
        Constructor<Singleton> declaredConstructor = Singleton.class.getDeclaredConstructor(String.class, int.class);
        declaredConstructor.setAccessible(true);
        Singleton singleton = declaredConstructor.newInstance();
        System.out.println(singleton);
    }

在这里插入图片描述

反序列化破坏

    public static void main(String[] args) throws Exception {
        try {
            // 通过反序列化破坏
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test"));
            oos.writeObject(Singleton.uniqueInstance);
            oos.close();
            File file = new File("test");
            ObjectInputStream ins = new ObjectInputStream(new FileInputStream(file));
            Singleton singleton1 = (Singleton) ins.readObject();
            System.out.println(singleton1 == Singleton.uniqueInstance);
            file.delete();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值