单例模式学习笔记

本文介绍了Java中的单例模式,比较了饿汉模式(类加载即初始化)和懒汉模式(首次调用时初始化),讨论了线程安全问题及如何通过volatile和synchronized解决,以及如何避免反射攻击,最后提到使用枚举实现线程安全的单例。
摘要由CSDN通过智能技术生成

是什么?

在Java中,单例模式是一种常用的设计模式,用于确保一个类只有一个实例,并提供一个全局访问点。然而,当多个线程同时访问单例对象时,可能会引发线程安全性的问题。

饿汉单例模式实现

类被加载的时候就立即初始化并创建唯一实例

public class StarvingSingleton {
    private static final StarvingSingleton starvingSingleton = new StarvingSingleton();
    private StarvingSingleton(){ }
    public static StarvingSingleton getInstance(){
        return starvingSingleton;
    }

}

        成员变量设置为static确保唯一性,设置为private防止以StarvingSingleton.starvingSingleton的方式获取实例,设置为final确保成员变量一经初始化就无法被改变。构造方法私有,客户端就无法用new来创建对象

随后提供一个唯一可以访问成员变量的方法,使客户端可以获取早已初始化的实例

        这样我们在其它类中只能通过调用StarvingSingleton.getInstance()方法来获取实例,StarvingSingleton.starvingSingleton和构造函数来new对象的方法已经被堵死了。

public class SingletonDemo {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        System.out.println(StarvingSingleton.getInstance());
        System.out.println(StarvingSingleton.getInstance());
    }
}

输出结果表明,两次调用StarvingSingleton.getInstance()方法获取的实例是同一个,也是唯一的实例

懒汉单例模式实现

被客户端首次调用的时候才创建唯一实例

public class LazyDoubleCheckSingleton {
    private volatile static LazyDoubleCheckSingleton instance;

    private LazyDoubleCheckSingleton(){}

    public static LazyDoubleCheckSingleton getInstance(){

        //第一次检测
        if (instance==null){
            //同步
            synchronized (LazyDoubleCheckSingleton.class){
                if (instance == null){
                    //memory = allocate(); //1.分配对象内存空间
                    //instance(memory);    //2.初始化对象
                    //instance = memory;   //3.设置instance指向刚分配的内存地址,此时instance!=null
                    instance = new LazyDoubleCheckSingleton();
                }

            }
        }
        return instance;
    }

 

volatile 关键字,用于声明变量。当一个变量被 volatile 修饰时,表明这个变量是易变的,在多线程环境下,每次访问该变量都会直接从内存中读取,而不会使用线程的本地缓存。volatile 的主要作用是确保变量的可见性和禁止指令重排序。

  1. 可见性:当一个变量被声明为 volatile 时,对这个变量的修改会立即被其他线程所感知,即使是在多个线程之间的操作也能保证可见性。

  2. 禁止指令重排序:volatile 关键字还可以禁止指令重排序,这意味着在 volatile 变量的赋值操作后面会有一个内存屏障,这可以防止代码的执行顺序被重新排序。

volatile 不能保证原子性。如果一个操作涉及到了多个变量的读取和写入,并且需要保证原子性,那么就需要使用 synchronized 或者 Lock 等同步机制。

在多线程编程中,当多个线程共享一个变量时,如果其中一个线程修改了这个变量,其他线程可能无法立即感知到这个变量的变化,这就可能导致数据不一致的问题。使用 volatile 关键字可以解决这个问题,确保变量的可见性,从而避免了这类问题的发生。

同样的懒汉的类构造函数设置为私有的,防止外部调用构造函数创建实例,定义LazyDoubleCheckSingleton类型的成员变量作为单例返回给客户端,但是该单例在类加载的时候并不初始化,而是第一次调用才初始化,同时提供了唯一获取单例的方法getInstance()

两次if判断确保线程安全,synchronized给该类上一个同步锁,一个线程通过第一个if检查后上锁,其他线程只能等待锁的释放。

        因为初始对象,分配空间这两步会被程序执行器任意分配执行顺序,导致只分配了空间没初始化就因为被其他线程访问,跳过上锁的内容返回了没有初始化的instance。所以使用volatile修饰,保证顺序执行。

 然而,private可以被反射突破,如饿汉模式通过反射

public class SingletonDemo {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        System.out.println(StarvingSingleton.getInstance());
        Class clazz = StarvingSingleton.class;
        Constructor constructor = clazz.getDeclaredConstructor();
        constructor.setAccessible(true);
        System.out.println(StarvingSingleton.getInstance());
    }
}

 这里,两次输出的实例并不是同一个,懒汉模式亦是如此

public class SingletonDemo {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        System.out.println(LazyDoubleCheckSingleton.getInstance());
        Class clazz = LazyDoubleCheckSingleton.class;
        Constructor constructor = clazz.getDeclaredConstructor();
        constructor.setAccessible(true);
        System.out.println(LazyDoubleCheckSingleton.getInstance());
    }
}

 想要不被反射突破,改写饿汉模式,使用枚举类型

public class EnumStarvingSingleton {
    private EnumStarvingSingleton(){}
    public static EnumStarvingSingleton getInstance(){
        return ContainerHolder.HOLDER.instance;
    }
    private enum ContainerHolder{
        HOLDER;
        private EnumStarvingSingleton instance;
        ContainerHolder(){
            instance = new EnumStarvingSingleton();
        }
    }

}

这样就不会被反射攻破

public class SingletonDemo {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        System.out.println(EnumStarvingSingleton.getInstance());
        Class clazz = EnumStarvingSingleton.class;
        Constructor constructor = clazz.getDeclaredConstructor();
        constructor.setAccessible(true);
        EnumStarvingSingleton enumStarvingSingleton = (EnumStarvingSingleton)constructor.newInstance();
        System.out.println(enumStarvingSingleton.getInstance());
    }
}

  • 12
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值