单例模式的五种写法

一、饿汉式单例

饿汉式单例模式,实例对象在初始化的时候创建,不管有没有得到调用。好处是没有线程安全的问题,坏处是浪费内存空间。

//饿汉式单例
public class ehan {

    /**
     * 存在的问题:
     * 饿汉式强调的是一上来就初始化对象,初始化的对象里面又有很多像如下定义的很多数组同时也被初始化,
     * 但没有得到调用,就会存在浪费空间的情况
     * */
    private byte[] data1 = new byte[1024*1024];
    private byte[] data2 = new byte[1024*1024];
    private byte[] data3 = new byte[1024*1024];
    private byte[] data4 = new byte[1024*1024];


    //构造器私有,保证别的方法无法调用生成这个对象
    private ehan(){}

    //实例对象初始化时创建
    private static final ehan HUNGRY = new ehan();

    public static ehan getInstance(){
        return HUNGRY;
    }

    public static void main(String[] args) {
        ehan ehan1 = ehan.getInstance();
        ehan ehan2 = ehan.getInstance();
        System.out.println(ehan1==ehan2);//true
    }

}

二、懒汉式

我们知道饿汉式在初始化的时候就完成了对象的创建,但存在浪费空间的问题,因此,懒汉式单例模式应运而生。
懒汉式顾名思义就是只有当需要的时候才会去初始化数据。

//懒汉式单例模式
public class lanhan {
    //构造器私有
    private lanhan(){
    }

    private static lanhan lazyMan;

    public static lanhan getInstance(){
        if(lazyMan==null){
            lazyMan = new lanhan();
        }
        return lazyMan;
    }

    public static void main(String[] args) {

        lanhan instance1 = lanhan.getInstance();
        lanhan instance2 = lanhan.getInstance();
        System.out.println(instance1==instance2);
    }


}

三、DCL(Double Check Lock)懒汉式

由于上述的懒汉式在单线程里确实是安全的,但是在多线程中,其实是并不安全的,我们可以通过以下代码测试:
我们会发现,得出的结果并不是单例的。

//懒汉式单例模式
public class lanhan {
    //构造器私有
    private lanhan(){
        System.out.println(Thread.currentThread().getName()+"OK");
    }

    private static lanhan lazyMan;

    public static lanhan getInstance(){
        if(lazyMan==null){
            lazyMan = new lanhan();
        }
        return lazyMan;
    }

    /**
     * 单线程下确实单例安全,但是多线程下是存在安全问题的
     *  我们得加锁
     * */
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                lanhan.getInstance();
            }).start();
        }

        TimeUnit.SECONDS.sleep(3);

        lanhan instance1 = lanhan.getInstance();
        lanhan instance2 = lanhan.getInstance();
        System.out.println(instance1==instance2);

    }

}

因此我们得给他上锁,保证一个线程只能创建一个对象,也有且只能有一个对象。同时为了防止指令重排现象出现,我们得对变量加volatile锁。

//DCL懒汉式单例模式
public class lanhan {
    //构造器私有
    private lanhan(){
        System.out.println(Thread.currentThread().getName()+"OK");
    }
	//防止指令重排
    private static volatile lanhan lazyMan;

    //双重检测锁模式--DCL懒汉式检测
    public static lanhan getInstance(){
        //先判断有没有对象实例被创建,没有的话就锁住这个类,确保只有一个Class对象
        if(lazyMan==null){
            synchronized (lanhan.class){
                if(lazyMan==null){
                    //但是还可能存在一个问题,因为new lanhan()不是一个原子操作
                    /*
                     * 1.分配内存
                     * 2.执行构造方法
                     * 3.指向地址
                     * 有可能存在的问题就是由于指令重排,它的执行顺序完全有可能是132,这个时候由于指向了内存地址,
                     * 会被以为创建了对象,导致无法创建对象,为了保证不发生指令重排现象,我们必须加上volatile关键字
                     * */
                    lazyMan = new lanhan();
                }
            }
        }
        return lazyMan;
    }
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                lanhan.getInstance();
            }).start();
        }

        TimeUnit.SECONDS.sleep(3);

        lanhan instance1 = lanhan.getInstance();
        lanhan instance2 = lanhan.getInstance();
        System.out.println(instance1==instance2);

    }

}

四、静态内部类

可以延时加载,并且线程安全

//静态内部类实现
public class Holder {
    //构造器私有
    private Holder(){

    }

    public static Holder getInstance(){
        return InnerClass.HOLDER;
    }

    //静态内部类
    public static class InnerClass{
    private static final Holder HOLDER = new Holder();
    }

    public static void main(String[] args) {
        Holder instance = Holder.getInstance();
        Holder instance2 = Holder.getInstance();
        System.out.println(instance==instance2);
    }

}

五、反射破坏单例模式

java中存在反射机制,能够改变内部的private关键字,因此使用private修饰的其实都不算是安全的,由此引出枚举单例模式。

//反射破坏单例模式
public class fanshe {
    //构造器私有
    private fanshe(){
        System.out.println(Thread.currentThread().getName()+"OK");
    }

    private static volatile fanshe lazyMan;

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

    public static void main(String[] args) throws Exception {
        fanshe instance = fanshe.getInstance();
        //利用反射获取其对应的构造器方法
        Constructor<fanshe> declaredConstructor = fanshe.class.getDeclaredConstructor(null);
        //无视私有构造器,通过反射创建对象
        declaredConstructor.setAccessible(true);
        fanshe instance2 = declaredConstructor.newInstance();

        System.out.println(instance==instance2);

    }
}

如何解决反射破坏单例现象?第一种方法,我们可以在构造器里面对类加锁。

//反射破坏单例模式
public class fanshe {
    //构造器私有
    private fanshe(){
       synchronized (fanshe.class){
           if(lazyMan==null){
               throw new RuntimeException("不要试图使用反射破坏异常");
           }
       }
    }

    private static volatile fanshe lazyMan;

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

    public static void main(String[] args) throws Exception {
        fanshe instance = fanshe.getInstance();
        //利用反射获取其对应的构造器方法
        Constructor<fanshe> declaredConstructor = fanshe.class.getDeclaredConstructor(null);
        //无视私有构造器,通过反射创建对象
        declaredConstructor.setAccessible(true);
        fanshe instance2 = declaredConstructor.newInstance();

        System.out.println(instance==instance2);

    }

}

但是上面那种方法治标不治本,因为第一个instance我们是通过getInstance方法创建的,没经过反射,如果我们两个对象都是通过反射创建,如下代码所示,还是会存在问题。

//反射破坏单例模式
public class fanshe {
   //构造器私有
    private fanshe(){
       synchronized (fanshe.class){
           if(lazyMan!=null){
               throw new RuntimeException("不要试图使用反射破坏异常");
           }
       }
    }

    private static volatile fanshe lazyMan;

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

    public static void main(String[] args) throws Exception {
        //fanshe instance = fanshe.getInstance();
        //利用反射获取其对应的构造器方法
        Constructor<fanshe> declaredConstructor = fanshe.class.getDeclaredConstructor(null);
        //无视私有构造器,通过反射创建对象
        declaredConstructor.setAccessible(true);
        fanshe instance2 = declaredConstructor.newInstance();
        fanshe instance3 = declaredConstructor.newInstance();
        System.out.println(instance3==instance2);

    }
}

由此引入第二种方法,通过定义一个信号量来解决:

//反射破坏单例模式
public class fanshe {

    //定义一个信号量
    private static boolean flag = false;

    //构造器私有
    private fanshe(){
       synchronized (fanshe.class){
           //通过反射的方式无法获取到这个信号量,因此不能通过反射创建
           if(flag==false){
               flag=true;
           }
          else{
               throw new RuntimeException("不要试图使用反射破坏异常");
           }
       }
    }

    private static volatile fanshe lazyMan;

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

    public static void main(String[] args) throws Exception {
        //fanshe instance = fanshe.getInstance();
        //利用反射获取其对应的构造器方法
        Constructor<fanshe> declaredConstructor = fanshe.class.getDeclaredConstructor(null);
        //无视私有构造器,通过反射创建对象
        declaredConstructor.setAccessible(true);
        fanshe instance2 = declaredConstructor.newInstance();
        fanshe instance3 = declaredConstructor.newInstance();
        System.out.println(instance3==instance2);

    }

}


六、枚举单例

利用枚举的方式创建单例模式,可以避免反射对其内部进行关键字的破坏,继而导致单例模式失效。它的缺点也是不能延时加载

public enum meiju{
    INSTANCE;
    public meijugetInstance(){
        return INSTANCE;
    }
}
class test05{
    public static void main(String[] args) {
        meijuinstance1 = SingletonTest05.INSTANCE;
        meijuinstance2 = SingletonTest05.INSTANCE;
        System.out.println(instance1==instance2);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值