java中实现单例模式的几种方法

java中实现单例模式的几种方法,第1张

java中实现单例模式的几种方法

        先来说说什么是单例模式,单例模式是23种设计模式之一,它可以保证全局中,一个类中只有一个实例,节省了内存空间,供全局使用,共用一个实例对象,而不会被外部实例。

常用的实现单例模式的方法之一:

饿汉式单例模式:在类初始化的时候就进行了对象的创建

package com.lai.single;


public class Hungry {

    private Hungry(){};

    private final static Hungry hungry=new Hungry();

    public static Hungry getInstance(){
        return hungry;
    }

}

缺点:正如它的特点,即在类初始化的时候就创建了,如果没有使用到就造成了内存空间的浪费

懒汉式单例模式:

package com.lai.single;


public class LazySingle {

    private LazySingle(){
        System.out.println("创建成功!");
        synchronized (LazySingle.class){
            if (lazySingle!=null){
                throw new RuntimeException("破坏反射异常!");
            }
        }
    }

    private volatile static LazySingle lazySingle;

    //双重检查锁模式 的懒汉式单例,DCL懒汉式
    public static  LazySingle getInstance(){

        if (lazySingle==null) {
            synchronized (LazySingle.class) {
                if (lazySingle==null) {
                    if (lazySingle == null) {
                        lazySingle = new LazySingle();

                    }
                }
            }
        }

        return lazySingle;
    }
}

以上单例模式使用双重检查的机制,并且使用了volatile这个java内置关键字防止了指令重排

在这里再讲一下volatile,它是java的关键字之一,具有以下三个特点:

1、保证可见性:

当被volatile关键字修饰的字段发生改变时,其它使用到该字段的地方也会发生改变

2、不保证原子性:

即在高并发的环境下多个线程同时 *** 作是不安全的,要使其安全需要使用同步机制或者lock锁

3、防止指令重排:

因为你写的程序,计算机可能不是按照你写的代码顺序执行的,正如上面懒汉式创建对象(lazySingle = new LazySingle())的过程,并不是一步完成,而是分几步:

(1)为对象分配内存空间

(2)执行相应的构造方法,完成对象的初始化

(3)将对象指向所分配的内存空间

如果指令重排了创建对象可能的指向顺序就会变成(1)(3)(2),而此时可能未完成对象初始化,但是第一个if判断结果不为空,而将对象返回了出去,导致出现问题。

为了防止通过反射破坏单例,我在构造方法中加了一层判断,当有一个实例的时候再使用反射破坏构造方法获取对象会抛出异常,但这种方式并不能完全防止单例被破坏,当直接通过反射创建对象时这种方法也不能防止,因为此时创建出来的对象跟lazySingle并无关系 。

通过反射破坏单例:

package com.lai.single;

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


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

        Class lazySingleClass = LazySingle.class;
        Constructor constructor = lazySingleClass.getDeclaredConstructor(null); //获取无参构造器
        constructor.setAccessible(true); //破坏构造器的私有化
        LazySingle lazySingle = LazySingle.getInstance();
        LazySingle lazySingle1 = constructor.newInstance(null);

        System.out.println(lazySingle==lazySingle1); //结果为true

    }
}

但通常情况下我们也不会闲着没事干通过反射破坏我们自己创建的单例。

静态内部类单例:

package com.lai.single;

import javax.xml.ws.Holder;


public class Inner {

    private Inner(){}

    public static class InnerClass{
        private static final Inner inner=new Inner();
    }

    public static Inner getInstance(){
        return InnerClass.inner;
    }
}

通过静态内部类的方式也是实现单例的一种方法,只有当调用Inner类的getInstance方法时才完成对象的创建。

最安全的单例方法:

枚举类单例:

package com.lai.single;

import org.omg.CORBA.PUBLIC_MEMBER;


public enum EnumSignle {

    INSTANCE;

    public static EnumSignle getInstance(){
        return INSTANCE;
    }
}

在使用反射破坏类的私有化企图创建对象的时候,会进行判断,当发现创建对象的类是枚举类的时候,会抛出异常,创建对象失败。从这可以看出,枚举类好似天生的单例,是最安全的。

但在java中,有个特殊的类,在sun.misc这个包下叫Unsafe类,之所以叫这个名字,我理解是因为它太强大了,可以破坏安全,所以叫不安全类,因为通过此类的特殊方法allocateInstance可以创建任何类的实例,包括枚举类,一下为结合反射获取任何类的实例的方法,因此,可以说没有绝对安全的单例。

Constructor unsafeConstructor = Unsafe.class.getDeclaredConstructor(null);
unsafeConstructor.setAccessible(true);
Unsafe unsafe = unsafeConstructor.newInstance(null);
EnumSignle instance1 = EnumSignle.getInstance();
EnumSignle a = (EnumSignle)unsafe.allocateInstance(EnumSignle.class);

今天,关于创建单例的几种方法就说到这里,有疑问的小伙伴欢迎在下方评论区留言哦~

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/zaji/5672294.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-12-16
下一篇2022-12-17

发表评论

登录后才能评论

评论列表(0条)

    保存