单例模式详解

**

单例模式

一个类就一个实例,自己new这个实例对象(不允许别人new),并为该实例提供一个全局访问点(写静态方法,通过类名.静态方法名去调),核心是无参构造方法私有。

1.饿汉单例

//饿汉模式单例
public class Hungry {
    //私有的无参构造,别人就不能通过new来获得单例对象 只能用Hungry.getInstance
    private Hungry() {

    }

    private static Hungry hungry = new Hungry();

    public static Hungry getInstance() {
        return hungry;
    }

}

饿汉单例相比于懒汉,他是先new实例的,所以饿汉调实例速度快,并且他没有懒汉那样的线程安全问题,缺点就是比较耗费资源,比如我只是访问了类中的一个静态变量,就会使得这个类去new实例出来(即使用不到它)。

2.懒汉单例

public class Lazy {
    //加一个红绿灯 防止多次反射创建单例
    private static boolean flag=false;//但是这个flag也会可以通过反射改值

    private Lazy(){
        //无参构造私有防不了反射
        //解决方法 抛异常
        synchronized (Lazy.class){
            if(flag==false){
                flag=true;
            }else {
                throw new RuntimeException("反射创建单例异常");
            }
        }

    };
    private static volatile Lazy lazy;

    //双端检测,保证单例
    public static Lazy getInstance(){
        if(lazy==null){
            synchronized (Lazy.class){
                if(lazy==null){
                    lazy=new Lazy();//不是一个原子性操作
                    /*
                    1.给对象在堆上分配内存空间
                    2.执行构造方法,初始化对象
                    3.引用指向刚刚分配好的空间

                    我们希望123
                    但是实际可能是132
                    当线程A执行到了13时候,线程B进来,发现lazy!=null,就直接return没有初始化的lazy了
                    空指针异常就会出现
                    故这里还要加上volatile禁止指令重排


                    */
                }
            }
        }

        return lazy;
    }

//反射破坏单例,因为反射可以进入私有的构造器创建对象
    public static void main(String[] args) throws Exception {
        //正常方法拿单例
        Lazy instance1 = Lazy.getInstance();
        Lazy instance2 = Lazy.getInstance();
        //反射的方法拿单例,强行走了私有的构造方法
        Constructor<Lazy> declaredConstructor = Lazy.class.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);
        Lazy instance3 = declaredConstructor.newInstance();

        System.out.println(instance1==instance2);//true
        System.out.println(instance1==instance3);//false


    }
}

懒汉单例问题就多了,先谈一下它的好处,懒加载,用到的时候再new,比较省资源。
为了避免多线程创建多个不同的单例对象,要双端检测加sychronized,另外由于new对象它不是一个原子性操作,为了防止报空指针异常,还需要加volatile关键字去禁止指令重排。最骚的是,通过反射可以无视掉你的私有方法去获得单例,我们这里可以加一个flag(红绿灯),去防止反射多次创建单例对象,但是道高一丈魔高一尺,就连那个flag,我也可以通过反射的方法给你改值。所以这里就引入了枚举中的单例,在newInstance这个方法的源码里,有这么一句

throw new IllegalArgumentException("Cannot reflectively create enum objects");

就是说枚举的单例是禁止通过反射的方法获取实例对象的。

//枚举可以把常量包装成对象
//通过枚举创建的单例不能通过反射调用
public enum EnumSingle {

    INSTANCE;

    public EnumSingle getInstance() {
        return INSTANCE;
    }
}


class Test {
    public static void main(String[] args) throws Exception {
        //正常方法创建单例实例
        EnumSingle instance1 = EnumSingle.INSTANCE;
        //反射创建单例实例
        Constructor<EnumSingle> declaredConstructor =
                EnumSingle.class.getDeclaredConstructor(String.class, int.class);//枚举没有无参构造,必须传参
        declaredConstructor.setAccessible(true);
        EnumSingle instance2 = declaredConstructor.newInstance();
        //IllegalArgumentException: Cannot reflectively create enum objects
        System.out.println(instance1 == instance2);
    }
}

结果是

Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects

3.内部类写单例

//静态内部类
public class Holder {
    private Holder(){};


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


    //静态内部类,懒加载,用到才会加载这个静态内部类
    static class InnerClass{
        private static Holder holder=new Holder();
    }

    public static void main(String[] args) {
        Holder holder=Holder.getInstance();
    }
}

在内部类中去new,有懒汉的优点,用到才会去加载这个内部类。但缺点就是还是没有饿汉快(毕竟他饿么,他早就端好碗了)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值