java单例模式

单例模式

单例模式基本分为两种:饿汉式和饱汉式

还有一些比较好玩的值得研究的场景。

饿汉式

package com.pihao.main.single;

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

    private HungryMan(){
    }
    private static HungryMan hungryMan = new HungryMan();

    private static HungryMan getInstance(){
        return hungryMan;
    }

    //思考:这种写法在该对象还没使用的时候就要先去加载庞大的资源,造成浪费
    private byte[] data1 = new byte[1024 * 1024];
    private byte[] data2 = new byte[1024 * 1024];

}

饱汉式/懒汉式

package com.pihao.main.single;

/**
 * 饱汉式、懒汉式
 */
public class FullMan {
    private FullMan(){
    }
    private static FullMan fullMan;

    public static FullMan getInstance(){
        if(fullMan == null){
            //要用的时候再去加载new
            fullMan = new FullMan();
        }
        return fullMan;
    }

}

单例模式的安全性验证

package com.pihao.main.single;

/**
 * 饱汉式、懒汉式
 */
public class FullMan {
    private FullMan(){
        System.out.println(Thread.currentThread().getName() + "启动了");
    }
    private static FullMan fullMan;

    public static FullMan getInstance(){
        if(fullMan == null){
            //要用的时候再去加载
            fullMan = new FullMan();
        }
        return fullMan;
    }

    //上述代码在单线程下没问题的,但是在多线程的场景下如果保证单例呢?

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                FullMan.getInstance();
            }).start();
        }
    }
}


//执行效果如下:
Thread-1启动了
Thread-2启动了
Thread-0启动了

结论:在并发的情况下,懒汉式的单例模式是有问题的,不能保证获取到的是同一个对象。
解决办法:上锁

双重检测锁模式的懒汉式单例----DCL懒汉式

package com.pihao.main.single;

/**
 * 饱汉式、懒汉式
 */
public class FullMan {
    private FullMan(){
        System.out.println(Thread.currentThread().getName() + "启动了");
    }
    //使用volatile关键字,防止指令重排
    private volatile static FullMan fullMan;

    public static FullMan getInstance(){
        if(fullMan == null){
            //加锁
            synchronized (FullMan.class){
                if(fullMan == null){
                    //要用的时候再去加载
                    fullMan = new FullMan();
                }
            }
        }
        return fullMan;
    }

    //上述代码在单线程下没问题的,但是在多线程的场景下如果保证单例呢?

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                FullMan.getInstance();
            }).start();
        }
    }
}


//执行效果如下:
Thread-0启动了

结论:在常规并发的情况下,只能获取到同一个对象。解决了上述的问题

静态内部类

package com.pihao.main.single;

public class Outer {
    private Outer(){
    }
    public static Outer getInstance(){
        return InnerClass.OUTER;
    }
    
    public static class InnerClass{
        private static final Outer OUTER = new Outer();
    }
}

利用反射破坏单例

package com.pihao.main.single;

import java.lang.reflect.Constructor;

/**
 * 饱汉式、懒汉式
 */
public class FullMan {
    private FullMan(){
    }
    private volatile static FullMan fullMan;

    public static FullMan getInstance(){
        if(fullMan == null){
            synchronized (FullMan.class){
                if(fullMan == null){
                    //要用的时候再去加载
                    fullMan = new FullMan();
                }
            }
        }
        return fullMan;
    }

    //利用反射破坏单例
    public static void main(String[] args) throws Exception {
        FullMan instance1 = FullMan.getInstance();
        //无参构造
        Constructor<FullMan> constructor = FullMan.class.getDeclaredConstructor(null); 
        constructor.setAccessible(true);
        FullMan instance2 = constructor.newInstance();
        System.out.println(instance1.hashCode());
        System.out.println(instance2.hashCode());
    }
}

//输出为
1163157884
1956725890
说明创建了两个不同的对象,单例不安全,怎么解决呢?
//上述问题解决办法
package com.pihao.main.single;

import java.lang.reflect.Constructor;

/**
 * 饱汗式、懒汉式
 */
public class FullMan {
    private FullMan(){
        synchronized (FullMan.class){
            if(fullMan != null){
                throw  new RuntimeException("非法构建对象!");
            }
        }
    }
    private volatile static FullMan fullMan;

    public static FullMan getInstance(){
        if(fullMan == null){
            synchronized (FullMan.class){
                if(fullMan == null){
                    //要用的时候再去加载
                    fullMan = new FullMan();
                }
            }
        }
        return fullMan;
    }
}

继续进阶

上述代码是一个new 对象,一个利用反射来创建的对象,那么如果我两个对象都是用反射来创建的呢?还会是同一个值吗?

package com.pihao.main.single;

import java.lang.reflect.Constructor;

/**
 * 饱汗式、懒汉式
 */
public class FullMan {
    private FullMan(){
        synchronized (FullMan.class){
            if(fullMan != null){
                throw  new RuntimeException("非法构建对象!");
            }
        }
    }
    private volatile static FullMan fullMan;

    public static FullMan getInstance(){
        if(fullMan == null){
            synchronized (FullMan.class){
                if(fullMan == null){
                    //要用的时候再去加载
                    fullMan = new FullMan();
                }
            }
        }
        return fullMan;
    }

    //利用反射破坏单例
    public static void main(String[] args) throws Exception {
        Constructor<FullMan> constructor = FullMan.class.getDeclaredConstructor(null); //无参构造
        constructor.setAccessible(true);
        FullMan instance1 = constructor.newInstance();
        FullMan instance2 = constructor.newInstance();
        System.out.println(instance1.hashCode());
        System.out.println(instance2.hashCode());
    }
}

//输出为
1163157884
1956725890

发现又创建了两个不同的对象,怎么解决呢?可以添加一个属性,类似红绿灯的方式
package com.pihao.main.single;

import java.lang.reflect.Constructor;

/**
 * 饱汗式、懒汉式
 */
public class FullMan {
    private static boolean flag = false;
    private FullMan(){
        synchronized (FullMan.class){
            if(flag == false){
                flag = true;
            }else{
                //进来两次就报错
                throw  new RuntimeException("非法构建对象!");
            }
        }
    }
    private volatile static FullMan fullMan;

    public static FullMan getInstance(){
        if(fullMan == null){
            synchronized (FullMan.class){
                if(fullMan == null){
                    //要用的时候再去加载
                    fullMan = new FullMan();
                }
            }
        }
        return fullMan;
    }

    //利用反射破坏单例
    public static void main(String[] args) throws Exception {
        Constructor<FullMan> constructor = FullMan.class.getDeclaredConstructor(null);
        constructor.setAccessible(true);
        FullMan instance1 = constructor.newInstance();
        FullMan instance2 = constructor.newInstance();
        System.out.println(instance1.hashCode());
        System.out.println(instance2.hashCode());
    }
}

//使用上述的标志后确实可以解决多次反射创建对象的问题,但是。。。。。。

思考:在第一次使用反射创建完instance1对象之后,是否可以利用反射将属性flag 的值重新设置为false,那么之后又可以创建instance2对象了。。。道高一尺,魔高一丈!

那么最终的办法是怎么解决才能保证安全呢?

枚举!!

枚举本身也是一个class类

package com.pihao.main.single;

import java.lang.reflect.Constructor;

public enum EnumSingle {
    INSTANCE;

    public EnumSingle getInstance(){
        return EnumSingle.INSTANCE;
    }

    public static void main(String[] args)throws Exception {
//Constructor<EnumSingle> constructor = EnumSingle.class.getDeclaredConstructor(null);
//Exception in thread "main" java.lang.NoSuchMethodException: com.pihao.main.single.EnumSingle.<init>()
        //上面的报错是因为底层没有空参构造这个方法,虽然看class类有这个方法,可以使用jad反编译一下

        EnumSingle instance1 = EnumSingle.INSTANCE;
        Constructor<EnumSingle> constructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
        constructor.setAccessible(true);
        EnumSingle instance2 = constructor.newInstance();
//报错: Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
    }

}

//结论:枚举类不会被反射破坏,使用枚举创建单例模式是安全的
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值