单例模式笔记

单例模式

前言

单例模式是指:确保一个类在任何情况下都绝对只有一个实例,隐藏其构造的方法,并提供一个全局访问点

例如ServletContext、ServletConfig、ApplicationContext、DBPool

你能记起多少单例?

饿汉式,饱汉式,双重检查式,静态内部类式

饿汉式

缺点:当需要加载的单例对象数量过多时,会造成内存浪费

==注:==如果是变成static代码块去new也是没有本质区别的——因为都是一开始就初始化了,不过至少可以放上对应的初始化的操作,还是比原生的要好一点点的

/**
 * @Author if
 * @Description: 饿汉式单例:先创建好了,随时可以等着使用
 * 线程安全,效率高,但是不能延迟加载
 * @Date 2021-09-20 下午 08:19
 */
public class DanLiDemo01 {
    private static DanLiDemo01 instance=new DanLiDemo01();
	private DanLiDemo01() {}
    public static DanLiDemo01 getInstance() {
        return instance;
    }
}

饱汉式

/**
 * @Author if
 * @Description: 饱汉式单例:等待调用,调用里判断是否存在,不存在就创建
 * 线程安全,效率较低,每次需要判断,且为了安全性需要加同步锁
 * 但尽管这样做到了线程安全,并且解决了多实例问题,但并不高效
 * 所以我们为此添加一个优化类————双重检查单例
 * @Date 2021-09-20 下午 08:22
 */
public class DanLiDemo02 {
    private static DanLiDemo02 instance=null;
    private DanLiDemo02() {}
    public synchronized static  DanLiDemo02 getInstance(){
        if(instance==null){
            instance=new DanLiDemo02();
        }
        return instance;
    }
}

双重检查式

参考链接

/**
 * @Author if
 * @Description: 双重检查单例
 * @Date 2021-09-20 下午 08:45
 */
public class DanLiDemo04 {
    /**
     * 在早期的JVM中,synchronized(甚至是无竞争的synchronized)存在着巨大性能开销。
     * 因此,出现双重检查锁定。以下是示例代码。
     * 将饱汉式优化为如下的双重检查类(此方法有问题)
     *
     * 如上面的代码所示,如果第一次检查instance不为null,那就不需要执行下面的加锁和初始化操作。
     * 因此,可以大幅降低synchronized带来的性能开销。
     * 这样似乎很完美,但这是一个错误的优化!
     * 在线程执行到第3行,代码读取到instance不为null时,instance引用的对象可能还没有完成初始化。
     */
    private static DanLiDemo04 instance;
   
    public  static DanLiDemo04 getInstance(){
        if(instance ==null) {
            synchronized (DanLiDemo04.class) {
                if (instance == null) {
                    instance = new DanLiDemo04();
                }
            }
        }
        return instance;
    }

    private static void doSomething(){

    }
    /**
     * 那如何进行优化呢?
     * 首先,先看一段代码
     * 这段代码是很典型的一段代码,很多人在中断线程时可能都会采用这种标记办法
     * 每个线程在运行过程中都有自己的工作内存,那么线程1在运行的时候
     * 所以线程1会将stop变量的值拷贝一份放在自己的工作内存当中
     * 那么当线程2更改了stop变量的值之后,但是还没来得及写入主存当中,线程2转去做其他事情了
     * 那么线程1由于不知道线程2对stop变量的更改,因此还会一直循环下去
     *
     * 但是用volatile修饰之后就变得不一样了
     * 1.使用volatile关键字会强制将修改的值立即写入主存
     * 2.使用volatile关键字的话,当线程2进行修改时,会导致线程1的工作内存中缓存变量stop的缓存行无效
     * (反映到硬件层的话,就是CPU的L1或者L2缓存中对应的缓存行无效)
     * 3.由于线程1的工作内存中缓存变量stop的缓存行无效,所以线程1再次读取变量stop的值时会去主存读取
     * 即线程2修改了stop的值后,线程1读取不到缓存的值,会需要去主存读取,此时stop为true,线程1停止
     */
    public static void main(String[] args) {
        //线程1
        boolean stop = false;
        while(!stop){
            doSomething();
        }
        //线程2
        stop = true;
    }
    /**
     * 基于volatile关键词的优化后的双重检查单例,即把实例声明为volatile
     * 当instance2被改变后,立即消除线程工作内存的缓存,使其读取主存的地址
     * 当然使用静态内部类单例更为简洁和方便
     */
    private volatile static DanLiDemo04 instance2;
    private DanLiDemo04() {}
    public static DanLiDemo04 getInstance2(){
        if(instance ==null) {
            synchronized (DanLiDemo04.class) {
                if (instance == null) {
                    instance = new DanLiDemo04();
                }
            }
        }
        return instance;
    }
}

静态内部类式

/**
 * @Author if
 * @Description: 静态内部类单例模式
 * 线程安全,效率高,只在调用时创建且不需要进行判断
 * 与饿汉、饱汉的区别:
 * 外部类没有static属性,所以不会像饿汉式一样立即创建对象
 * 只有在调用到getInstance时才会加载内部类Instance,才会进行创建对象并返回
 * 对象是static final的,所以保证了内存地址中只会有一个实例对象,保证了安全性
 * 兼备了并发高效调用和延迟加载的优势
 * @Date 2021-09-20 下午 08:24
 */
public class DanLiDemo03 {
    private static class Instance{
        public static final DanLiDemo03 instance=new DanLiDemo03();
    }
    private DanLiDemo03() {}
    public static DanLiDemo03 getInstance(){
        return Instance.instance;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值