并发编程下的单例模式

单例模式

保证一个类只有一个实例。
特点:构造函数是私有的

饿汉式

饿汉式是线程安全的,因为只会装载一次,在装载的时候是不会发生并发的。
但容易产生垃圾对象,在类加载时,就完成了初始化,浪费内存。

/**
 * @description
 * 饿汉式
 */
public class HungryMan {

    /**
     * 预先定义好对象
     * 饿汉式是线程安全的,因为虚拟机保证只会装载一次,在装载类的时候是不会发生并发的。
     * 但如果并没有使用该类,则造成内存空间的浪费
     */
    private static HungryMan hungryMan = new HungryMan();

    private HungryMan(){

    }
    /**等到需要时直接返回*/
    public static HungryMan getInstance(){
        return hungryMan;
    }
}

懒汉式

单线程懒汉

这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。

/**
 * @description
 * 适用于单线程下的懒汉式
 */
public class LazyMan {
    private static LazyMan lazyMan;
    private LazyMan(){

    }
    public static LazyMan getInstance(){
        if (lazyMan==null){
            lazyMan=new LazyMan();
        }
        return lazyMan;
    }
}
synchronized懒汉

通过给方法加上同步锁,使得每次能够进行赋值的只有一个线程
而且对于方法加锁,使得不管怎么样,都得排队等待,效率降低。

虽然保证了线程安全,但仍然存在遭到反射破坏的可能。

public class LazyMan {
    private static LazyMan lazyMan;
    private LazyMan(){

    }
    public static synchronized LazyMan getInstance(){
        if (lazyMan==null){
            lazyMan=new LazyMan();
        }
        return lazyMan;
    }
}
双重校验懒汉式

通过锁对象的方式,提高了执行效率。
是多线程下安全的懒汉模式
虽然保证了线程安全,但仍然存在遭到反射破坏的可能

public class LazyMan {
    private volatile static LazyMan lazyMan;
    private LazyMan(){

    }
    public static  LazyMan getInstance(){
        if (lazyMan==null){
            synchronized (LazyMan.class){
                if(lazyMan==null){
                    lazyMan=new LazyMan();
                    /**再执行该操作时,该操作并不是一个原子性操作*/
                    //1.分配内存空间
                    //2.初始化对象
                    //3.对象的引用指向这个空间
                    /**即,在多线程下操作,可能进行123,但也可能进行132,
                     * 当发生132的时候,就会出现问题。
                     * 当新的线程想去拿引用的时候,会出现拿到的引用里的对象还没有初始化的情况。
                     * 所以要拿volatile关键词,保证这个对象的一个可见性
                     */
                }
            }
        }
        return lazyMan;

    }
}
内部静态类之懒汉

使用内部静态类的方式,利用了 classloader 机制来保证初始化 instance 时只有一个线程。因为LazyMan加载的时候,LazyManHolder不一定会初始化,只有在调用getInstance方法的时候,才会显式的装载LazyManHolder

虽然保证了线程安全,但仍然存在遭到反射破坏的可能

public class LazyMan {
    private static class LazyManHolder{
        private static final LazyMan lazyMan = new LazyMan();
    }
    private LazyMan(){

    }
    public static final LazyMan getInstance(){
        return LazyManHolder.lazyMan;
    }
}
反射破坏

通过反射,可以重新使用构造,避开了getInstance的方法。

class test{
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null);
        constructor.setAccessible(true);
        LazyMan instance1 =LazyMan.getInstance();
        LazyMan instance2 =constructor.newInstance();
        LazyMan instance3 =constructor.newInstance();

        System.out.println("类="+instance1);
        System.out.println("反射1="+instance2);
        System.out.println("反射2="+instance3);
    }
}

在这里插入图片描述

简单避免反射破坏单例(但如果判断静态变量泄漏仍有可能遭到反射破坏)

在上述的代码中加入一个判断静态变量,并将构造函数改成下面这样的,就可以避免反射破坏单例。

	private static boolean lazy =false;
    
    private LazyMan(){
        synchronized (LazyMan.class){
            if(lazy==false){
                lazy=true;
            }else{
                throw new RuntimeException("不要试图用反射破坏异常");
            }
        }

    }

在这里插入图片描述
比如你的反射这么写,也是可以改掉静态变量的值的

class test{
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
        Field field = LazyMan.class.getDeclaredField("lazy");
        field.setAccessible(true);
        Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null);
        constructor.setAccessible(true);
        LazyMan instance1 =LazyMan.getInstance();
        field.set("lazy",false);
        LazyMan instance2 =constructor.newInstance();
        field.set("lazy",false);
        LazyMan instance3 =constructor.newInstance();

        System.out.println("类="+instance1);
        System.out.println("反射1="+instance2);
        System.out.println("反射2="+instance3);
    }
}

在这里插入图片描述

枚举类之懒汉(也可避免反射)

jdk1.5推出的枚举类,可以很简单的实现多线程下安全的懒汉式单例模式
而且其本身就避免了反射造成的破坏。

public enum  LazyEnum {
    LAZY_ENUM;
    public LazyEnum getInstance(){
        return LAZY_ENUM;
    }
}
class test{
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        LazyEnum instance1 = LazyEnum.LAZY_ENUM;
        //注意枚举类本身的构造器,是带有两个参数的
        Constructor<LazyEnum> constructor = LazyEnum.class.getDeclaredConstructor(String.class,int.class);
        constructor.setAccessible(true);
        LazyEnum instance2 =constructor.newInstance();
        LazyEnum instance3 =constructor.newInstance();

        System.out.println("类="+instance1);
        System.out.println("反射1="+instance2);
        System.out.println("反射2="+instance3);
    }
}

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值