单例模式的理解与学习笔记

单例模式,顾名思义就是只有一个实例,并且她自己负责创建自己的对象,这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象

1. 懒汉式:需要的时候在创建

public class Danli {
    /*懒汉式*/
    private static Danli lanhan = null;
    private Danli(){};
    public static Danli getLanhan(){
        if(null == lanhan){
            lanhan =  new Danli();
        }
        return lanhan;
    }

    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newFixedThreadPool(20);
        for (int i = 0; i< 20; i++) {
            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Danli.getLanhan());
                }
            });
        }
        threadPool.shutdown();
    }
}

结果:
在这里插入图片描述
我们发现在创建20个对象的时候,出现了不一样的对象,但是单例模式要求只有一个对象,这样就违背了单例模式原则。
原因:if(null == lanhan){
lanhan = new Danli();
}
几个线程同时进入上面这个方法,重复执行了构造方法,造成线程不安全
解决办法
办法一:

public static synchronized Danli getLanhan(){
        if(null == lanhan){
            lanhan =  new Danli();
        }
        return lanhan;
    }

加sychronized关键字,这种解决方式,假如有100个线程同时执行,那么,每次去执行getLanhan()方法时都要先获得锁再去执行方法体,如果没有锁,就要等待,耗时长,感觉像是变成了串行处理。
方法二:

private volatile static Danli lanhan = null;   //加上volatile关键字,防止指令重排
public static synchronized Danli getLanhan(){
        if(null == lanhan){
            synchronized (Danli.class){
                if(null == lanhan){
                    lanhan = new Danli();
                }
            }
        }
        return lanhan;
    }

加同步代码块,双重检测锁,减少锁的颗粒大小。
这里会涉及到一个指令重排序问题。instance = new Singleton2()其实可以分为下面的步骤:
1.申请一块内存空间;
2.在这块空间里实例化对象;
3.instance的引用指向这块空间地址;
指令重排序存在的问题是:
对于以上步骤,指令重排序很有可能不是按上面123步骤依次执行的。比如,先执行1申请一块内存空间,然后执行3步骤,instance的引用去指向刚刚申请的内存空间地址,那么,当它再去执行2步骤,判断instance时,由于instance已经指向了某一地址,它就不会再为null了,因此,也就不会实例化对象了。这就是所谓的指令重排序安全问题。那么,如何解决这个问题呢?
加上volatile关键字,因为volatile可以禁止指令重排序。

2. 饿汉式:先创建好

    /*饿汉式*/
    private volatile static Danli ehan = new Danli();
    private Danli(){};
    public static Danli getEhan(){
        return ehan;
    }

Java 的类只会初始化一次(这次初始化是初始化静态变量跟静态常量,运行时常量不包含在内)并且是线程安全的(由 JVM 保证),初始化的顺序是有父类优先初始化父类的 filed 跟静态代码块,然后是接着初始化子类 filed 跟静态代码块,filed 跟静态代码块的初始化顺序是根据你定义的顺序初始化。而触发类初始化的触发条件分别是:调用静态变量、调用静态方法、构建类实例、对静态变量进行赋值、反射获取 Class信息、初始化一个类的子类、JVM 启动标记是启动类的类。最后,恶汉模式因为静态变量是在初始化的时候赋值,而类初始化是线程安全的,不存在多个线程初始化多次的问题,因此是线程安全的。

3. 静态内部类

public class Danli {
    /*静态内部类*/
    private static class JingTai{
        private static final Danli INSTANCE  = new Danli();
    }
    private Danli(){ }
    public static final Danli getInstance(){
        return JingTai.INSTANCE;
    }
}

注意:
以上几种方法在反射的情况下还是线程不安全,因为反射可以创建对象

    public static void main(String[] args) throws Exception {
        Constructor<Danli> constructor = Danli.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        Danli danli = constructor.newInstance();
    }

4. 枚举

所以引入枚举类型,枚举是不支持反射的。
jdk8中,在newInstance()方法的newInstanceWithCaller()方法中有这么一段代码
在这里插入图片描述

参考:
https://www.cnblogs.com/sunnyDream/p/8011186.html
https://www.zhihu.com/question/371657547
https://www.cnblogs.com/saoyou/p/11087462.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值