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

23种设计模式之单例模式

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

23种设计模式之单例模式

饿汉式与懒汉式 饿汉式
  • 不让用户去自主的创建实例对象,而是自己创建好实例对象,让用户通过设置好的方法去调用
package com.cx;


public class SingletonHung {

    //事先准备实例对象
    private static SingletonHung singletonHung=new SingletonHung();
    
    //构造器私有,用户不能直接通过构造器来创建对象了
    private SingletonHung() {
    }

    //提供一个方法对外使用
    public static SingletonHung getSingletonHung(){
        return singletonHung;
    }

}

懒汉式
  • 使用的时候再去创建
package com.cx;


public class SingletonLazy {

    private static SingletonHung singletonHung=null;

    //构造器私有
    private SingletonLazy() {
    }

    public static SingletonHung singletonHungInstance(){
        if (singletonHung==null){
            singletonHung=new SingletonHung();
        }
        return singletonHung;
    }
}

注意:上述在多线程中,容易出现线程安全问题

DCL懒汉式

DCL单例模式:Double Check Lock缩写,即双重检查锁定,是单例模式中线程安全的懒汉式单例模式

package com.cx;


public class SingletonLazy {
    private static SingletonLazy singletonLazy=null;

    //构造器私有
    private SingletonLazy() {
    }

    //双重检测模式
    public static SingletonLazy singletonLazyInstance(){
        if (singletonLazy==null){
            synchronized (SingletonLazy .class){
                if (singletonLazy==null){
                    singletonLazy=new SingletonLazy();
                }
            }
        }
        return singletonLazy;
    }
    
}


此时是否就安全了?
极端情况下,此模式也不安全,原因如下:

singletonHung=new SingletonHung();

此步骤不是一个原子性操作,实际上是有三步操作:

1. 分配内存空间
2. 执行构造方法,初始化对象
3. 把该对象指向这个空间

那么可能就会出现指令重排,即,若线程A执行顺序为1-3-2,线程B执行顺序为1-2-3;那么当线程A执行到1-3时,此时还没有实际生成对象,但是线程B认为singletonHung!=null,那么就会直接走return,那么线程B的得到的是没有完成构造的对象。那么现在为了避免指令重排,就要使用volatile关键字避免指令重排

package com.cx;


public class SingletonLazy {

    //volatile 避免指令重排
    private static volatile SingletonLazy singletonLazy=null;

    //构造器私有
    private SingletonLazy() {
    }

    //双重检测模式
    public static SingletonLazy singletonLazyInstance(){
        if (singletonLazy==null){
            synchronized (SingletonLazy.class){
                if (singletonLazy==null){
                    singletonLazy=new SingletonLazy();
                }
            }
        }
        return singletonLazy;
    }
    
}

那么经过上述操作了之后,是不是就安全了?
显然也不是很安全,因为还有一种方法可以破坏此模式

反射破坏单例模式1
package com.cx;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class Test29 {

    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {

        SingletonLazy singletonLazyInstance = SingletonLazy.getSingletonLazyInstance();

        Constructor declaredConstructor = SingletonLazy.class.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);//关掉权限检测,就可以使用私有
        SingletonLazy singletonLazy = declaredConstructor.newInstance();
        //得到的两个对象不一样
        System.out.println(singletonLazyInstance);
        System.out.println(singletonLazy);

    }
}


可以知道该种方法是通过反射,得到私有构造器,通过私有构造器来创建对象,所以我们可以通过对构造器加一些控制来避免这种方式的破坏

避免通过反射破坏单例
package com.cx;


public class SingletonLazy {

    //volatile 避免指令重排
    private static volatile SingletonLazy singletonLazy=null;

    //构造器私有
    private SingletonLazy() {
        if (singletonLazy!=null){
            throw new RuntimeException("不要视图通过使用反射破坏异常!");
        }
    }

    //双重检测模式
    public static SingletonLazy getSingletonLazyInstance(){
        if (singletonLazy==null){
            synchronized (SingletonLazy.class){
                if (singletonLazy==null){
                    singletonLazy=new SingletonLazy();
                }
            }
        }
        return singletonLazy;
    }


}

那么如果两个都使用反射来构造对象?

反射破坏单例模式2
package com.cx;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class Test29 {

    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {

        Constructor declaredConstructor = SingletonLazy.class.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);//关掉权限检测,就可以使用私有
        SingletonLazy singletonLazy = declaredConstructor.newInstance();
        SingletonLazy singletonLazy2 = declaredConstructor.newInstance();
        //得到的两个对象不一样
        System.out.println(singletonLazy);
        System.out.println(singletonLazy2);
    }
}


那如何避免这种情况?可以通过添加标志量来避免

避免通过反射破坏单例
package com.cx;


public class SingletonLazy {

    //标志位
    private static boolean flag=false;

    //volatile 避免指令重排
    private static volatile SingletonLazy singletonLazy=null;

    //构造器私有
    private SingletonLazy() {
        if (flag==false){
            flag=true;
        }else{
            throw new RuntimeException("不要视图通过使用反射破坏异常!");
        }
        if (singletonLazy!=null){
            throw new RuntimeException("不要视图通过使用反射破坏异常!");
        }
    }

    //双重检测模式
    public static SingletonLazy getSingletonLazyInstance(){
        if (singletonLazy==null){
            synchronized (SingletonLazy.class){
                if (singletonLazy==null){
                    singletonLazy=new SingletonLazy();
                }
            }
        }
        return singletonLazy;
    }


}

那么此做法是不是就不会再出错了?显然不是,我们还可以通过反射获取其私有属性,进行修改

反射破坏单例模式3
package com.cx;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;

public class Test29 {

    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {

        Constructor declaredConstructor = SingletonLazy.class.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);//关掉权限检测,就可以使用私有
        Field flag = SingletonLazy.class.getDeclaredField("flag");
        flag.setAccessible(true);
        SingletonLazy singletonLazy = declaredConstructor.newInstance();
        flag.set("flag",false);//又将其修改回来
        SingletonLazy singletonLazy2 = declaredConstructor.newInstance();
        //得到的两个对象不一样
        System.out.println(singletonLazy);
        System.out.println(singletonLazy2);
    }
}


…
可以发现,某前使用的这些方法,总会有另一种方法来破坏单例模式,那是否有一种操作,使其无法通过反射来构造对象,从根本上解决这个问题?
答案是有的,那就是通过枚举,在通过构造器的newInstance来创建对象中,明确的表示:Cannot reflectively create enum objects

@CallerSensitive
    public T newInstance(Object ... initargs)
        throws InstantiationException, IllegalAccessException,
               IllegalArgumentException, InvocationTargetException
    {
        if (!override) {
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, null, modifiers);
            }
        }
        if ((clazz.getModifiers() & Modifier.ENUM) != 0)
            throw new IllegalArgumentException("Cannot reflectively create enum objects");
枚举类实现单例模式

Java虚拟机会保证枚举类型不能被反射并且构造函数只被执行一次。

package com.cx;

public enum SingletonEnum {
    INSTANCE;

    //volatile 避免指令重排
    private volatile User user = null;

    //构造器私有
    private SingletonEnum() {
    
        user = new User();
    }

    //提供单例
    public User getUser(){
        return user;
    }
}

package com.cx;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class Test29 {

    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {


        User user = SingletonEnum.INSTANCE.getUser();
        User user2 = SingletonEnum.INSTANCE.getUser();
        //一模一样,构造器只会执行一次
        System.out.println(user.hashCode());
        System.out.println(user2.hashCode());

        System.out.println("------------------");
        Constructor declaredConstructor = SingletonEnum.class.getDeclaredConstructor(String.class,int.class);
        declaredConstructor.setAccessible(true);

        //这一步就会报异常 Cannot reflectively create enum objects
        SingletonEnum singletonEnum = declaredConstructor.newInstance();

        User user3 = singletonEnum.getUser();
        System.out.println(user3.hashCode());

    }
}


java-单例模式 推荐狂神说

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

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

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