栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

java设计模式-单例模式

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

java设计模式-单例模式

定义 饿汉式

定义:类加载的时候就创建对象。
优点:简单,无线程安全问题。
缺点:浪费空间,不够灵活。

public class Hungry {

    public final static Hungry INSTANCE = new Hungry();

    private Hungry() {
    }
}
饿汉式 静态代码块
public class HungryStatic {


    public final static HungryStatic INSTANCE;

    static {
        INSTANCE = new HungryStatic("例如从配置文件读入");
    }

    private HungryStatic(String config) {
    }

    private String config;

    public String getConfig() {
        return config;
    }

    public void setConfig(String config) {
        this.config = config;
    }
}
枚举

优点:线程安全,不会被反射破坏。

public enum Enum {
    
    INSTANCE;
    
}
懒汉式 (线程不安全)

定义:在使用的时候如果没有创建过则创建。
优点:节约空间。
缺点:存在线程安全问题。

public class Lazy {

    private static Lazy instance;

    private Lazy() {
    }

    public static Lazy getInstance() {
        if (instance == null) {
            instance = new Lazy();
        }
        return instance;
    }
}
懒汉式 双检锁 DCL
public class LazySafe {

    private static volatile LazySafe instance;

    private LazySafe() {
    }

    public static LazySafe getInstance() {
        if (instance == null) {
            synchronized (LazySafe.class) {
                if (instance == null) {
                    instance = new LazySafe();
                }
            }
        }
        return instance;

    }
}
为什么需要第二次判断

因为 如果两个线程 同时判断 instance == null ,开始争抢锁,第一个线程执行过后,如果不加锁,线程二会直接new 一个新对象,所以必须加锁保证只有一个对象创建 。

为什么要加 volatile

一句话总结就是防止拿到 instance == null 的 对象。

反编译一条 new 指令可以拿到以下的结果。

步骤4 初始化对象,步骤7 指针指向该对象,由于 cpu存在指令重排序,先完成指针指向的话就会产生null对象的情况,所以需要禁止指令重排序保证对象初始化之后在完成指向的动作。

懒汉式 静态内部类

原理 : 静态内部类的延迟初始化,且类只会加载一次。

public class LazyInner {

    private LazyInner() {
    }

    private static class Inner {
        private static final LazyInner INSTANCE = new LazyInner();
    }

    public static LazyInner getInstance() {
        return Inner.INSTANCE;
    }

}

The “Double-Checked Locking is Broken” Declaration

破坏单例 多线程破坏单例

多线程环境下会有很多线程同时进行 if 判断,导致创建多个独享。

错误示例

虽然可以保证线程安全,但是方法性能太低,任意读请求都需要持有锁之后才可以进行相应操作。

正确示例

使用双检锁,或者内部类的形式保证线程安全。

指令重排序破坏单例

双检锁不使用 volatile 关键字修饰,就会产生重排序情况的产生。

克隆破坏单例 反序列化破坏单例 反射破坏单例

结论:使用枚举类来实现单例,防止反射破坏单例。

破坏实例

我们以Runtime为例来演示使用反射破坏单例。

  		Class aClass = Runtime.class;
        Constructor cons = aClass.getDeclaredConstructor();
        cons.setAccessible(true);
        Runtime runtime = cons.newInstance();
        Runtime runtime2 = cons.newInstance();
        System.out.println(runtime);
        System.out.println(runtime2);
        ----
java.lang.Runtime@568db2f2
java.lang.Runtime@378bf509
解决方案

优先使用枚举来解决反射破坏单例的问题。

我们来获取一下枚举类中的构造器

        Class aClass = Enum.class;
        Constructor[] constructors = aClass.getDeclaredConstructors();
        // Constructor cons = aClass.getDeclaredConstructor();
        Constructor cons2 = aClass.getDeclaredConstructor(String.class, int.class);
        // cons.setAccessible(true);
        cons2.setAccessible(true);
        // Enum e1 = cons.newInstance();
        // Enum e2 = cons.newInstance();
        Enum e3 = cons2.newInstance();
        Enum e4 = cons2.newInstance();

可以看到里面会生成一个String , int 参数类型的构造器

idea 反编译结果跟预期明显不同。

javap 反编译 class 文件后发现也没有相应的构造方法

我们更换反编译工具,使用 jad 进行后续的测试

jad下载地址


将 jad.exe 放在 jdk 的 bin 目录下即可

jad 反编译后成功看到Enum类中 String 和 int 参数的构造函数。

单例在java的应用场景 Runtime

饿汉式实现单例。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1006515.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号