单例模式以及线程安全问题

//单例模式 懒汉模式的实现

class SingletonLazy {
private static SingletonLazy instance = null;
private static Object locker = new Object();

public static SingletonLazy getInstance() {
  // if (instance == null) {
        //synchronized (locker) {
            if (instance == null) {
                instance = new SingletonLazy();
            }
       // }
   // }
    return instance;
}
public SingletonLazy() {}

}
public class Demo28 {

public static void main(String[] args) {
    SingletonLazy s1 = SingletonLazy.getInstance();
    SingletonLazy s2 = SingletonLazy.getInstance();
    System.out.println(s1 == s2);
}

}



package thread;
//单例模式 懒汉模式的实现

class SingletonLazy {
private static SingletonLazy instance = null;
private static Object locker = new Object();

public static SingletonLazy getInstance() {
  // if (instance == null) {
        //synchronized (locker) {
            if (instance == null) {
                instance = new SingletonLazy();
            }
       // }
   // }
    return instance;
}
public SingletonLazy() {}

}
public class Demo28 {

public static void main(String[] args) {
    SingletonLazy s1 = SingletonLazy.getInstance();
    SingletonLazy s2 = SingletonLazy.getInstance();
    System.out.println(s1 == s2);
}

}


## 单例模式多线程环境下的安全问题


在多线程中 饿汉式单例模式是线程安全的 而 懒汉式单例模式是线程不安全的  
 为什么呢 因为饿汉式写法 创建实例的时机是在Java线程启动(比main调用还早的时机)再后续线程执行获取对象的时候 意味着实例早就已经存在了 每个线程的获取操作就做了一件事 读取代码中静态变量的值  
 多个线程读取同一个变量的值 线程是安全的


懒汉式 则涉及到读和修改操作 就是要先判断instance里面的引用地址是否为空 为空才修改 多线程环境下可能就会产生bug  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/41bdd528ba8f4c5684fbf3631b46d4ec.png)  
 像上面图片这种执行顺序就会出现线程安全问题 就不止一个实例了 就不符合我们单例模式的初衷了。  
 那怎么办呢 我们最容易想到处理的方式就是加锁了 那要怎么加锁呢  
 比如这样加锁:  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/d663fcf25dd04640a7fc86020fd3f781.png)  
 这样很明显还是线程不安全:  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/e8833e30369b4f52afe62ee74746a683.png)  
 因为这个锁就相当于没加 两个线程还是会new两个对象 那怎么办呢 我们可不可以把if判断操作和new操作打包成一个原子 答案是当然可以。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/c75fd5afbb47488eb30363dd8a8c22c8.png)  
 像上面这样加锁 t1执行加锁之后 ,t2就会阻塞等待 直到t1释放锁(new完了)t2才拿到锁,才能进行条件判读 t2判断的时候instance早就非空了 ,也就不会再new了。  
 但是现在还存在一个问题 就是我们上面那种加锁虽然解决了线程安全问题 但是这样设计锁 每次调用那个getinstance方法,就需要先加锁,再执行后续操作。 但是懒汉模式只是一开始调用的时候存在线程安全问题 ,一旦实例创建好了,后续再调用就只是读取操作了 ,就不存在线程安全问题  
 但是我们这样加锁就会出现后面都没有线程安全问题了 但是我们还在加锁,这就有点画蛇添足了。因为锁本身也是有开销的可能会使线程阻塞。


那怎么办呢 我们可以引入双重if判定  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/2c61ae541bfd43d9b00b33ee5580e96a.png)  
 在上面这种加锁方式下 首先我们要先判断一下是否需要加锁 实例化之后线程安全了就不用加锁了 实例化之前就应该加锁 在两个if判断之间,synchronized会使线程阻塞等待 阻塞过程其他线程会修改instance的值


下面我们来画个**时间轴**来解释:


![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/8a8939d1ea924fa38348ac08095f71e0.png)  
 当t2在进去第一个if条件之后就会阻塞等待 等到t1释放锁 现在instace已经不为null了 t2的第二个if条件也是进不去的 后面不为空了 锁就不用加了。  
 这样就解决了没有线程安全也加锁的情况了。


但是现在还有一个问题 就是内存可见性引起的线程安全问题 就相当于  
 t1线程修改了instance引用,t2有可能读不到(不过这种概率应该很小)为了避免这种情况的发生 我们还要加上**volatile**关键字  
 这个关键字还可以解决指令重排序问题


##### 指令重排序


指令重排序也是编译器的一种优化策列 按照正常来说你写一段代码 cpu应该使按照顺序一条一条执行的 ,但是编译器就比较智能,会根据实际情况生成二进制指令的执行顺序,和你最初写的代码的顺序可能会存在差别  
 调整顺序最主要的目的就是为了提高效率,但是在保证逻辑是等价的。


![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/f5aeda5e620c4fe5ac29954d7eb71744.png)  
 上面执行这个代码会有三条指令  
 1、申请内存空间  
 2、调用构造方法(对内存空间进行初始化)  
 3、把此时内存空间的地址,赋值给instance引用


在多线程环境下 如果执行顺序是1 3 2 就会 出现线程安全问题  
 如果3指令比2指令先执行就会出现返回未初始化完毕的对象  
 就相当于t1线程执行完 instance就不是null了 但其实他是一个为初始化的对象 到时候t2线程执行的时候instance引用已经不是空的了 就进不去 就直接返回instance 了 返回了一个没有初始化完毕的对象 。这样就会导致很严重的线程安全问题 所以我们要加上**volatile**关键字 这样就很好的解决了指令重排序引起的线程安全问题。

## 最后

**自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。**

**深知大多数网络安全工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**

**因此收集整理了一份《2024年网络安全全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。**

![img](https://img-blog.csdnimg.cn/img_convert/43eea458d7aed9fb0a1920fecd3eb7d1.png)

![img](https://img-blog.csdnimg.cn/img_convert/fae78add940b2f2798468858f3a88cc1.png)

![img](https://img-blog.csdnimg.cn/img_convert/7352d048353a32cb0efa518d323127eb.png)

![img](https://img-blog.csdnimg.cn/img_convert/6fc7bbc26e30e50c0cc908c95d6f2705.png)

![img](https://img-blog.csdnimg.cn/img_convert/84e854280a3ea2750a4253511d9f7d92.png)

 

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上网络安全知识点!真正的体系化!**

[**如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!**](https://bbs.csdn.net/topics/618653875)

**由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!**

这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!**](https://bbs.csdn.net/topics/618653875)

**由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!**

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值