十八、单例模式程序员必会!!!
【Java JUC并发之单例模式的巧妙用法】1、饿汉式
//饿汉式单例public class Hungry {// 在饿汉式单例下 这些资源一起全部加载进来// 会造成空间浪费private byte[] data1 = new byte[1024];private byte[] data2 = new byte[1024];private byte[] data3 = new byte[1024];private byte[] data4 = new byte[1024];private Hungry() { // 构造器私有}private final static Hungry HUNGRY = new Hungry();public static Hungry getInstance() {return HUNGRY;}}饿汉式与懒汉式的本质区别:
- 饿汉式是 线程安全 的,在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变;懒汉式 如果在创建实例对象时不加上synchronized,则会导致对对象的访问不是 线程安全 的 。
- 从实现方式来讲:懒汉式是延时加载,只有在需要的时候才创建对象;
而饿汉式在虚拟机启动的时候就会创建,饿汉式无需关注多线程问题、写法简单明了、能用则用 。但是它是加载类时创建实例、所以如果是一个 工厂模式 ,缓存了很多实例、那么就得考虑效率问题,因为这个类一加载,则把所有实例不管用不用一块创建 。
双重检查锁(Double Checked Locking)
在单线程情况下,实现单例模式是安全的,但是如果考虑多线程,就可能会出现问题,导致出现多个LazyMan的实例!
原因:考虑可能有两个线程同时调用getInstance(),LazyMan就会被实例化两次 并且被不同对象持有,完全违背单例模式的初衷 。
解决方法一 加锁 :
// 懒汉式单例-加锁public class LazyMan {private LazyMan() {}private static LazyMan lazyMan;public static LazyMan getInstance() {synchronized (LazyMan.class){if (lazyMan == null) {lazyMan = new LazyMan();}}}return lazyMan;}}加锁虽然能解决问题,但是因为用到了synchronized,会导致很大的性能开销,并且每次初始化时都会加锁,性能浪费 。解决方法二、双重检查锁(有缺陷)
先判断对象是否已被初始化,再决定要不要加锁
// 懒汉式单例-加锁public class LazyMan {private LazyMan() {}private static LazyMan lazyMan;public static LazyMan getInstance() {if(lazyMan == null){synchronized (LazyMan.class){if (lazyMan == null) {lazyMan = new LazyMan();}}}}return lazyMan;}}这样写的话,运行顺序就会变成:- 检查变量是否被初始化(先不去获得锁),如果已被初始化则立即返回
- 获得锁
- 再次检查变量是否已被初始化,如果还没被初始化,就初始化一个对象
加双重检查的好处:除了初始化的时候会加锁,后续的所有调用都会避免枷锁,直接返回,解决了性能消耗的问题!
DCL 懒汉式仍然存在缺陷!
lazyMan = new LazyMan();
实例化上述对象的过程并不是原子性操作,它可以分为三步(编译器层面):
- 分配内存空间
- 初始化对象,执行构造方法
- 将对象指向刚才分配的内存空间
执行顺序可能会变成 1-3-2
假设A线程先通过第一次判断,获得锁,并且A线程调用顺序为1-3-2,执行完3之后,B线程刚好检查到 lazyMan 不为空,便返回一个未初始化完成的对象,于是产生了两个实例!
完整的DLC懒汉式=> 对lazyMan 加上 volatile 关键字 禁止指令重排
package com.liu.single;// 懒汉式单例public class LazyMan {private LazyMan() {System.out.println(Thread.currentThread().getName() + "LazyMan ok");}private volatile static LazyMan lazyMan; // volatile 禁止指令重排public static LazyMan getInstance() {// 双重检测锁模式懒汉式单例 DCL (Double Check Locking)if(lazyMan == null){synchronized (LazyMan.class){if (lazyMan == null) {lazyMan = new LazyMan(); // new 关键字 非原子性操作 可能会出现指令重排}}}return lazyMan;}public static void main(String[] args) {// 多线程并发// 不安全for (int i = 0; i < 10; i++) {new Thread(()->{LazyMan.getInstance();}).start();}}}小结 : 单例模式下使用volatile,可以禁止指令重排!?DLC + volatile 才是完整的懒汉式单例
静态内部类的懒汉式
package com.liu.single;// 使用静态内部类public class Holder {private Holder() {}private static Holder getInstance() {return InnerClass.HOLDER;}public static class InnerClass{private static final Holder HOLDER = new Holder();}}以上的单例都不安全 = > 因为有反射的存在,可以轻松破坏单例的安全性package com.liu.single;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.InvocationTargetException;// 懒汉式单例public class LazyMan02 {private static boolean liu = false; // 关键字可以加密 防止反射破坏private LazyMan02() {synchronized (LazyMan02.class) {if (liu == false) {liu = true;}else {throw new RuntimeException("不要试图用反射破坏代码!");}}//System.out.println(Thread.currentThread().getName() + "LazyMan ok");}private volatile static LazyMan02 lazyMan; // volatile 禁止指令重排public static LazyMan02 getInstance() {// 双重检测锁模式懒汉式单例 DCL (Double Check Locking)if(lazyMan == null){synchronized (LazyMan.class){if (lazyMan == null) {lazyMan = new LazyMan02(); // new 关键字 非原子性操作 可能会出现指令重排}}}return lazyMan;}public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {// 反射方法 :getDeclaredFiekd() 获得该类中声明的所有字段Field liu = LazyMan02.class.getDeclaredField("liu");// 反射 => 可以破坏单例//LazyMan02 instance = LazyMan02.getInstance();Constructor<LazyMan02> declaredConstructor = LazyMan02.class.getDeclaredConstructor();declaredConstructor.setAccessible(true);LazyMan02 instance = declaredConstructor.newInstance();liu.set(instance, false); // 反射可以破坏单例的安全性LazyMan02 instance02 = declaredConstructor.newInstance();System.out.println(instance);System.out.println(instance02);}}使用枚举类可以防止反射的破坏package com.liu.single;import java.lang.reflect.Constructor;import java.lang.reflect.InvocationTargetException;/** * Enum 枚举 * 本身也是一个类 */public enum EnumSingle {INSTANCE;private EnumSingle() {}public EnumSingle getInstance() {return INSTANCE;}public static void main(String[] args) throws InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException {EnumSingle instance = EnumSingle.INSTANCE;//java.lang.NoSuchMethodException:Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(null);declaredConstructor.setAccessible(true);EnumSingle instance2 = declaredConstructor.newInstance();System.out.println(instance);System.out.println(instance2);}}通过IDEA查看枚举类底层源码 => 无参构造器,使用反射之后,发现报的错误并不是我们预想的结果
文章插图
使用无参构造的Enum:报错 =>java.lang.NoSuchMethodException
预期报错: java.lang.IllegalArgumentException: Cannot reflectively create enum objects
Java反编译工具Jad 下载 :https://www.jianshu.com/p/5d8736d9a32a
使用命令javap -p EnumSingle.class进行编译:
可以看到编译之后的.java文件里面也是使用无参构造!

文章插图
枚举类型的最终反编译源码如下:
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.// Jad home page: http://www.kpdus.com/jad.html// Decompiler options: packimports(3) // Source File Name:EnumSingle.javapackage com.liu.single;import java.io.PrintStream;import java.lang.reflect.Constructor;import java.lang.reflect.InvocationTargetException;public final class EnumSingle extends Enum{public static EnumSingle[] values(){return (EnumSingle[])$VALUES.clone();}public static EnumSingle valueOf(String name){return (EnumSingle)Enum.valueOf(com/liu/single/EnumSingle, name);}private EnumSingle(String s, int i){super(s, i); // 证实枚举类使用的是有参构造,参数分别是String和int}public EnumSingle getInstance(){return INSTANCE;}public static void main(String args[])throws InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException{EnumSingle instance = INSTANCE;Constructor declaredConstructor = com/liu/single/EnumSingle.getDeclaredConstructor(null);declaredConstructor.setAccessible(true);EnumSingle instance2 = (EnumSingle)declaredConstructor.newInstance(new Object[0]);System.out.println(instance);System.out.println(instance2);}private static EnumSingle[] $values(){return (new EnumSingle[] {INSTANCE});}public static final EnumSingle INSTANCE = new EnumSingle("INSTANCE", 0);private static final EnumSingle $VALUES[] = $values();}将Enum类中通过反射传入两个参数作为构造器的参数 : String.class int.classpackage com.liu.single;import java.lang.reflect.Constructor;import java.lang.reflect.InvocationTargetException;public enum EnumSingle {INSTANCE;private EnumSingle() {}public EnumSingle getInstance() {return INSTANCE;}public static void main(String[] args) throws InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException {EnumSingle instance = EnumSingle.INSTANCE;Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class); // 使用String.class 和 int.class作为构造器的参数declaredConstructor.setAccessible(true);EnumSingle instance2 = declaredConstructor.newInstance();System.out.println(instance);System.out.println(instance2);}}其报错结果为:java.lang.IllegalArgumentException: Cannot reflectively create enum objects
即:无法通过反射来创建枚举类的实例=> 单例模式下使用枚举enum可以有效阻止反射,保证单例模式的安全性不被破坏!
本文来自博客园,作者:{夕立君},转载请注明原文链接:https://www.cnblogs.com/xili-sanriyue/p/15022438.html
- 春季老年人吃什么养肝?土豆、米饭换着吃
- 三八妇女节节日祝福分享 三八妇女节节日语录
- 老人谨慎!选好你的“第三只脚”
- 校方进行了深刻的反思 青岛一大学生坠亡校方整改校规
- 脸皮厚的人长寿!有这特征的老人最长寿
- 长寿秘诀:记住这10大妙招 100%增寿
- 春季老年人心血管病高发 3条保命要诀
- 眼睛花不花要看四十八 老年人怎样延缓老花眼
- 香槟然能防治老年痴呆症? 一天三杯它人到90不痴呆
- 老人手抖的原因 为什么老人手会抖
