大连企业建站程序,商务网站建设的步骤,wordpress套用主题,广东企业网站模板定制#x1f38d;目录 ⛳ 面试题-单例模式会存在线程安全问题吗#xff1f;#x1f3a8; 一、单例模式-简介#x1f69c; 二、饿汉式#x1f43e; 三、懒汉式#x1f3af; 3.1、懒汉式#xff1a;在调用 getInstance 的时候才创建对象。#xff08;线程不安全#xff09;目录 ⛳ 面试题-单例模式会存在线程安全问题吗 一、单例模式-简介 二、饿汉式 三、懒汉式 3.1、懒汉式在调用 getInstance 的时候才创建对象。线程不安全 3.2、改造1对懒汉式进行加锁改造线程安全 3.3、改造2对懒汉式继续改造。线程不安全 3.4、改造3改造成功对懒汉式再次改造。线程安全3.5、总结 四、内部静态类 4.1、反射攻击 4.2、反序列化攻击 ⭐ 五、枚举 六、总结 ⛳ 面试题-单例模式会存在线程安全问题吗 答会出现线程安全问题。 首先在Java中创建单例实例的方式有饿汉式、懒汉式、静态内部类、枚举等方式 饿汉模式是天生线程安全的饿汉模式在类创建的同时就创建好了一个静态对象又因为静态变量只会在类创建时执行一次所以创建好的实例不会再改变因此是线程安全的。 懒汉式不是线程安全的当多并发情形下可能会多个线程都创建实例不能保证单例模式可以改成双重校验锁既保证了调用效率又保证了线程安全。 静态内部类的方式相比于懒汉模式的优势是可以延迟加载因为只有在静态内部类被调用时JVM才会加载它同时保证了线程安全和调用效率。 以上三种的创建方式都不能解决反射、反序列化产生的线程安全问题 使用枚举天然的防止反射和反序列化既保证了线程安全又保证了调用效率但是不能延时加载 一、单例模式-简介 单例模式是 Java 中常用的设计模式之一属于设计模式三大类中的创建型模式。在运行期间保证某个类仅有一个实例并提供一个访问它的全局访问点。单例模式所属类的构造方法是私有的所以单例类是不能被继承的。实现线程安全的单例模式有以下几种方式有饿汉式、懒汉式、懒汉式改良版双重同步锁内部静态类、枚举 二、饿汉式 天生是线程安全的 但是无法应对反射、序列化的形式 饿汉式在类创建的同时就已经创建好了一个静态的对象供系统使用以后不在改变 public class Singleton {private static Singleton instance new Singleton();private Singleton() { // 私有的构造方法}public static Singleton getInstance() {return instance;}}这是实现一个安全的单例模式的最简单粗暴的写法所以称之为饿汉式
因为肚子饿了想要马上吃到东西不想等待生产时间。在类被加载的时候就把 Singleton 实例给创建出来以后不在改变。
饿汉式的优点和缺点
优点实现简单、线程安全调用效率高无锁且对象在类加载时就已创建可直接使用缺点可能在还不需要此实例的时候就已经把实例创建出来了不能延时加载在需要的时候才创建对象、使用反射序列化创建对象依然可以不是单例的 三、懒汉式 3.1、懒汉式在调用 getInstance 的时候才创建对象。线程不安全
public class Singleton { private static Singleton instancenull; private Singleton() {}; public static Singleton getInstance(){ if(instancenull){//可能会有多个线程进入代码块造成实例化多个对象instancenew Singleton(); } return instance; } }3.2、改造1对懒汉式进行加锁改造线程安全
public class Singleton { private static Singleton instancenull; private Singleton() {}; public static synchronized Singleton getInstance(){ if(instancenull){ instancenew Singleton(); } return instance; }
}但是这种方式并不推荐因为效率想对较低每个线程在执行 getInstance 的时候都要进行同步。而如果 instance 已经实例化了可以直接返回还需要进行改造 3.3、改造2对懒汉式继续改造。线程不安全
public class Singleton { private static Singleton instancenull; public static Singleton getInstance() { if (instance null) { synchronized (Singleton.class) { instance new Singleton(); } } return instance; }
}这种方式是线程不安全的假如 AB 两个线程同时进入到了 if(instance null)的代码块 A 线程拿到了锁进入 synchronized代码块对 instance 进行实例化结束并释放锁B 线程便拿到锁依然会进入到 synchronized代码块对 instance 进行实例化。那么这就对 instance 进行了两次实例化。出现了线程安全的问题。 3.4、改造3改造成功对懒汉式再次改造。线程安全 这种代码书写方式也称为 双重同步锁 public class Singleton { private static volatile Singleton instancenull; private Singleton(){}public static Singleton getInstance() { if (instance null) { synchronized (Singleton.class) { //在3.3的基础上多了一次判断避免了线程安全问题if (instance null) { instance new Singleton(); }} } return instance; }
}使用了double-check即check-加锁-check减少了同步的开销
在创建第一个对象时候可能会有线程1线程2两个线程进入getInstance()方法这时对象还未被创建所以都通过第一层check。接下来的synchronized锁只有一个线程可以进入假设线程1进入线程2等待。线程1进入后由于对象还未被创建所以通过第二层check并创建好对象由于对象singleton是被volatile修饰的所以在对singleton修改后会立即将singleton的值从其工作内存刷回到主内存以保证其它线程的可见性。线程1结束后线程2进入synchronized代码块由于线程1已经创建好对象并将对象值刷回到主内存所以这时线程2看到的singleton对象不再为空因此通过第二层check最后获取到对象。这里volatile的作用是保证可见性同时也禁止指令重排序因为上述代码中存在控制依赖多线程中对控制依赖进行指令重排序会导致线程不安全。
优点线程安全可以延时加载调用效率比锁加在方法上高。
另外需要注意 instance采用 volatile 关键字修饰也是很有必要。
instance采用 volatile 关键字修饰也是很有必要的 instance new Singleton(); 这段代码其实是分为三步执行 为 instance 分配内存空间初始化 instance 将 instance 指向分配的内存地址
但是由于 JVM 具有指令重排的特性执行顺序有可能变成 1-3-2。指令重排在单线程环境下不会出现问题但是在多线程环境下会导致一个线程获得还没有初始化的实例。
使用 volatile 可以禁止 JVM 的指令重排保证在多线程环境下也能正常运行。
3.5、总结 相比于饿汉式懒汉式显得没那么 “饿”在真正需要的时候在去创建实例。 懒汉式的优点和缺点
优点线程安全的可以延时加载。缺点调用效率不高有锁且需要先创建对象、使用反射序列化创建对象依然可以不是单例的 四、内部静态类
public class Singleton {private Singleton() {}public static Singleton getInstance() {return SingletonFactory.instance;}private static class SingletonFactory {private static Singleton instance new Singleton();}
} 静态内部类只有被主动调用的时候JVM才会去加载这个静态内部类。外部类初次加载会初始化静态变量、静态代码块、静态方法但不会加载内部类和静态内部类。
优点线程安全调用效率高可以延时加载。
似乎静态内部类看起来已经是最完美的方法了其实不是可以还存在反射攻击和反序列化攻击。 4.1、反射攻击
public class Singleton {private Singleton() {}public static Singleton getInstance() {return SingletonFactory.instance;}private static class SingletonFactory {private static Singleton instance new Singleton();}public static void main(String[] args) throws Exception {Singleton singleton Singleton.getInstance();ConstructorSingleton constructor Singleton.class.getDeclaredConstructor();constructor.setAccessible(true);Singleton newSingleton constructor.newInstance();System.out.println(singleton newSingleton);
}} 运行结果false
通过结果看这两个实例不是同一个违背了单例模式的原则 4.2、反序列化攻击
dependencygroupIdorg.apache.commons/groupIdartifactIdcommons-lang3/artifactIdversion3.8.1/version
/dependency这个依赖提供了序列化和反序列化工具类。
Singleton 类实现了 java.io.Serializable接口。
public class Singleton implements Serializable {private static class SingletonHolder {private static Singleton instance new Singleton();}private Singleton() {}public static Singleton getInstance() {return SingletonHolder.instance;}public static void main(String[] args) {Singleton instance Singleton.getInstance();byte[] serialize SerializationUtils.serialize(instance); // 序列化为一个数组Singleton newInstance SerializationUtils.deserialize(serialize); // 通过刚才序列化的数组进行反序列化System.out.println(instance newInstance);}}输出结果false表示不是一个实例
如果要解决 Singleton 类的实力在序列化和反序列化过程中仍然是唯一的需要添加一个readResolve()方法到 Singleton 类中以便在反序列化是返回相同的实例。如
private Object readResolve() throws ObjectStreamException {return getInstance();
}在 Java 中readResolve() 方法是一个特殊的方法用于在对象反序列化过程中控制返回的实例。它主要用于解决单例模式在反序列化时可能出现的问题。
⭐ 五、枚举 最佳的单例实现模式就是枚举模式。写法简单线程安全调用效率高可以天然的防止反射和反序列化调用不能延时加载。 public enum Singleton {INSTANCE;public void doSomething() {System.out.println(doSomething);}
} 调用方法
public class Main {public static void main(String[] args) {Singleton.INSTANCE.doSomething();}}直接通过Singleton.INSTANCE.doSomething()的方式调用即可。
枚举如何实现线程安全反编译后可以发现会通过一个类去继承该枚举然后通过静态代码块的方式在类加载时实例化对象与饿汉类似。https://blog.csdn.net/wufaliang003/article/details/81395411
如何做到防止反序列化调用每一个枚举类型及其定义的枚举变量在JVM中都是唯一的Java做了特殊的规定枚举类型序列化和反序列化出来的是同一个对象。
除此之外枚举还可以防止反射调用。 六、总结
综上线程安全的几种单例模式比较来看
枚举无锁调用效率高可以防止反射和反序列化调用不能延时加载 静态内部类无锁调用效率高可以延时加载 双重同步锁有锁调用效率高于懒汉式可以延时加载 懒汉式有锁调用效率不高可以延时加载~ 饿汉式无锁调用效率高不能延时加载
注只有枚举类型能防止反射和反序列化