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

java单例模式

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

java单例模式

java单例模式
  • 单例模式
    • 一、饿汉式
    • 二、懒汉式
      • Ⅰ:双层检测锁模式(DCL)
    • 三、静态内部类(扩充)
    • 四、反射破坏和枚举
      • Ⅰ:反射破坏单例模式
      • Ⅱ:枚举

单例模式
  • 单例模式可分为:饿汉式 、DCL懒汉式

注意: 构造器私有化是单例模式的一个重要思想。

一、饿汉式
public class Hungry {

    //单例模式 构造私有化,防止创建
    private Hungry(){}
	
    //创建对象信息(饿汉式:开始即创建)
    private final static Hungry hungry = new Hungry();
	
    //返回对象信息
    public static Hungry getInstance(){
        return  hungry;
    }
}
  • 缺点:饿汉式单例模式初始化创建就会加载所有的资源,所以可能会产生垃圾对象,浪费空间。
  • 优化:在使用时再进行创建加载,所以引出懒汉式单例模式。
二、懒汉式
public class Lazy {
    // 构造器私有化
    private Lazy(){}

    // 声明对象
    private static Lazy lazy;

    // 当lazy为null时,创建并返回lazy实例
    public static Lazy getInstance(){
        if(lazy ==null){
            lazy = new Lazy();
        }
        return lazy;
    }
}
  • 上述代码实现了在使用时创建对象的能力,相比于饿汉式,在实现上得到了优化。
  • 缺点:这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作
Ⅰ:双层检测锁模式(DCL)
public class Lazy {
    // 构造器私有化
    private Lazy(){
        System.out.println(Thread.currentThread().getName() + "ok");
    }
    // 声明对象
    private static Lazy lazy;
    // 双重检测锁模式的 懒汉式单例  DCL懒汉式
    public static Lazy getInstance(){
        if (lazy==null) {
            synchronized (Lazy.class){
                if(lazy ==null){
                    lazy = new Lazy();
                }
            }
        }
        return lazy;
    }
    // 主方法起10个线程调用
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                Lazy.getInstance();
            }).start();
        }
    }
}
  • 上述代码表面上是可以在多线程的场景下使用,但是还是存在一定的问题。
  • 当new Lazy()时,看似是一步操作,其实new的过程并不是一个原子操作。步骤如下:
  • 创建对象首先由内存创建空间。
  • 执行构造方法,初始化对象。
  • 将对象指向创建空间。
  • 创建对象过程可能会出现指令重排现象,比如我们期望是按从1–>2–>3的步骤执行,但是真实情况下可能会执行1–>3–>2。会先将对象指向空间,再去初始化对象。这样在第一个线程执行完毕之后,第二个线程执行时,lazy并不为null,这样就会返回一个lazy,但此时lazy并没有完成构造。
  • 优化: 为了安全,则需要保证lazy是避免指令重排的,所以要加上volatile关键字修饰lazy。
public class Lazy {
    // 构造器私有化
    private Lazy(){
        System.out.println(Thread.currentThread().getName() + "ok");
    }
    // 声明对象
    private volatile static Lazy lazy;
    // 双重检测锁模式的 懒汉式单例  DCL懒汉式
    public static Lazy getInstance(){
        if (lazy==null) {
            synchronized (Lazy.class){
                if(lazy ==null){
                    lazy = new Lazy();
                }
            }
        }
        return lazy;
    }
    // 主方法起10个线程调用
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                Lazy.getInstance();
            }).start();
        }
    }
}

优点: 这种方式采用双锁机制,安全且在多线程情况下能保持高性能。

三、静态内部类(扩充)
public class InnerSingle {

    private InnerSingle(){}

    public static class InnerClass{
        private static final InnerSingle innerSingle = new InnerSingle();
    }

    public static InnerSingle getInstance(){
        return InnerClass.innerSingle;
    }
}

描述: 这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。

思考: 上述三种方式都实现了单例模式,最优解目前看来就是懒汉式的双重检测锁模式实现的单例模式,那么问题来了,就算是懒汉式的双重检测锁模式的单例模式,是否就一定是完美的呢?

对于上述思考,我们就要考虑到java的反射是否可以破坏我们所写的单例模式,下面我们来探究一下。

四、反射破坏和枚举 Ⅰ:反射破坏单例模式
  • 我们在懒汉式的单例基础上,将主方法改为直接获取实例和通过反射来获取实例相对比,如下:
public static void main(String[] args) throws Exception {
        Lazy instance = Lazy.getInstance();
		
        // 使用反射获取空参构造器
        Constructor constructor = Lazy.class.getDeclaredConstructor(null);
        // 无视私有构造器
        constructor.setAccessible(true);
        // 通过反射创建实例
        Lazy instance1 = constructor.newInstance();
		
        System.out.println(instance);
        System.out.println(instance1);
    }
  • 运行结果:

com.studio.single.Lazy@1b6d3586

com.studio.single.Lazy@4554617c

Process finished with exit code 0

  • 可以很明显的看出来,此时两个实例并不相等,这无疑是反射破坏了单例模式。既然反射是通过构造器来创建,那么我们就试一下在构造器内再加上一把锁。如下:
// 构造器私有化
    private Lazy(){
       synchronized (Lazy.class){
           if(lazy != null){
               throw new RuntimeException("不要使用反射破坏单例");
           }
       }
    }
  • 运行结果:

Exception in thread “main” java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at com.studio.single.Lazy.main(Lazy.java:41)
Caused by: java.lang.RuntimeException: 不要使用反射破坏单例
at com.studio.single.Lazy.(Lazy.java:17)
… 5 more
Process finished with exit code 1

  • 在构造器上加锁,看起来是已经阻止了反射的破坏,但是我们的第一个实例还是通过单例来获取的,如果所有实例均使用反射来创建呢?构造器中的实例存在判定就不会存在,我们继续验证:
    public static void main(String[] args) throws Exception {
//        Lazy instance = Lazy.getInstance();
        // 使用反射获取空参构造器
        Constructor constructor = Lazy.class.getDeclaredConstructor(null);
        // 无视私有构造器
        constructor.setAccessible(true);
        // 通过反射创建实例
        Lazy instance = constructor.newInstance();
        Lazy instance1 = constructor.newInstance();
        System.out.println(instance);
        System.out.println(instance1);
    }
  • 运行结果:

com.studio.single.Lazy@1b6d3586
com.studio.single.Lazy@4554617c
Process finished with exit code 0

验证结论: 单例模式又被破坏了

  • 此时我们在考虑通过一个静态变量来设置门禁,修改代码如下:
	private  static boolean flag = false;
    // 构造器私有化
    private Lazy(){
       synchronized (Lazy.class){
           if(flag == false){
               flag = true;
           }else{
               throw new RuntimeException("不要使用反射破坏单例");
           }
       }
    }
  • 运行结果:

Exception in thread “main” java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at com.studio.single.Lazy.main(Lazy.java:45)
Caused by: java.lang.RuntimeException: 不要使用反射破坏单例
at com.studio.single.Lazy.(Lazy.java:20)
… 5 more
Process finished with exit code 1

验证结论: 此时我们又一次成功方式了反射对单例的破坏,但是既然是类内的常量,那么就能通过反射来进行变更,我们仍然不能阻止反射破坏单例。

public static void main(String[] args) throws Exception {
	// 反射获取常量
	Field flag = Lazy.class.getDeclaredField("flag");
	flag.setAccessible(true);
	// 使用反射获取空参构造器
	Constructor constructor = Lazy.class.getDeclaredConstructor(null);
	// 无视私有构造器
	constructor.setAccessible(true);
	// 通过反射创建实例
	Lazy instance = constructor.newInstance();
	// 在第一次创建实例后,flag重新设置为false
	flag.set(instance,false);
	Lazy instance1 = constructor.newInstance();
	System.out.println(instance);
	System.out.println(instance1);
}
Ⅱ:枚举
  • 枚举自带单例模式,且反射无法破坏枚举

说明: 枚举也是一个class类,只是继承了Enum

public enum EnumSingle {
    INSTANCE;
    public static EnumSingle getInstance(){
        return INSTANCE;
    }
}
class Test{
    public static void main(String[] args) {
        EnumSingle instance1 = EnumSingle.getInstance();
        EnumSingle instance2 = EnumSingle.getInstance();
        System.out.println(instance1);
        System.out.println(instance2);
    }
}
  • 验证结论:两个实例是相等的
  • 使用反射创建实例:
class Test{
    public static void main(String[] args) throws Exception {
        EnumSingle instance1 = EnumSingle.getInstance();
        Constructor declaredConstructor = EnumSingle.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        EnumSingle instance2 = declaredConstructor.newInstance();
        System.out.println(instance1);
        System.out.println(instance2);
    }
}
  • 验证结论:虽然抛出错误,无法对单例造成破坏,但错误为枚举类没有空参构造器。而我们的期望错误是:

throw new IllegalArgumentException(“Cannot reflectively create enum objects”);

  • 通过反编译工具,我们可以反向生成java文件,此时我们能够看到构造器代码如下:
private EnumSingle(String s,int i){
	super(s,i);
}
  • 构造器有两个参数,String和int,我们修改一下测试代码:
class Test{
    public static void main(String[] args) throws Exception {
        EnumSingle instance1 = EnumSingle.getInstance();
        Constructor declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
        declaredConstructor.setAccessible(true);
        EnumSingle instance2 = declaredConstructor.newInstance();
        System.out.println(instance1);
        System.out.println(instance2);
    }
}
  • 验证结论:虽然抛出错误,得到期望验证错误

Exception in thread “main” java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
at com.studio.single.Test.main(EnumSingle.java:23)

  • 总结: 这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/424810.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

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

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