加入收藏 | 设为首页 | 会员中心 | 我要投稿 核心网 (https://www.hxwgxz.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 建站 > 正文

你的Java并发程序Bug,100%是这几个原因造成的

发布时间:2019-10-30 17:38:55 所属栏目:建站 来源:平头哥
导读:可见性问题 可见性是指一个线程对共享变量进行了修改,其他线程能够立马看到该共享变量更新后的值,这视乎是一个合情合理的要求,但是在多线程的情况下,可能就要让你失望了,由于每个 CPU 都有自己的缓存,每个线程使用的可能是不同的 CPU ,这就会出现数

有序性:程序执行的顺序按照代码的先后顺序执行,比如下面这段代码

  1. 1  int i = 1; 
  2. 2  int m = 11; 
  3. 3  long x = 23L; 

按照有序性的话就需要按照代码的顺序执行下来,但是执行结果不一定是按照这个顺序来的,因为 JVM 为了提高程序的运行效率,会对上面的代码按照 JVM 编译器认为较好的顺序执行,从而可能打乱代码的执行顺序,是它会保证程序最终执行结果和代码顺序执行的结果是一致的,这也就是我们所说的指令重排序

由于指令重排序造成程序出 Bug 的典型案例就是:未加 volatile 关键字的双重检测锁单例模式,如下代码:

  1. public class Singleton { 
  2.     static Singleton instance; 
  3.     public static Singleton getInstance(){ 
  4.     // 第一次判断 
  5.     if (instance == null) { 
  6.         // 加锁,只有一个线程能够获取锁 
  7.         synchronized(Singleton.class) { 
  8.             // 第二次判断 
  9.             if (instance == null) 
  10.                 // 构建对象,这里面就非常有学问了 
  11.                 instance = new Singleton(); 
  12.             } 
  13.     } 
  14.     return instance; 
  15.     } 

双重检测锁方案看上去非常完美,但是在实际运行时却会出 Bug,会出现对象逸出的问题,可能会得到一个未构建完的 Singleton 对象, 这个就是在构建 Singleton 对象时指令重排序的问题。我们先来看看构建对象理想型的操作指令:

  • 指令1:分配一块内存 M;
  • 指令2:在内存 M 上初始化 Singleton 对象;
  • 指令3:然后 M 的地址赋值给 instance 变量。

但是实际在 JVM 编译器上可能不是这样,可能会被优化成如下指令:

  • 指令1:分配一块内存 M;
  • 指令2:将 M 的地址赋值给 instance 变量;
  • 指令3:最后在内存 M 上初始化 Singleton 对象。

看上去一个小小的优化,也就是这么一个小小的优化就会使你的程序不安全,假设抢到锁的线程执行完指令2 之后,此时的 instance 已经不为空了,这时候来了线程C,线程C 看到的 instance 已经是不为空的了,就会直接返回 instance 对象,这时候的 instance 并未初始化成功,调用 instance 对象的方法或者成员变量时将有可能触发空指针异常。可能的执行流程图:

你的Java并发程序Bug,100%是这几个原因造成的

未加 volatile 关键字的双重检测锁单例模式

上面就是造成 Java 程序在多线程情况下出 Bug 的三种原因,关于这些问题 JDK 公司也给出了相应的解决办法,具体如下图所示,这些解决办法的更多细节,我们后面在细细道来。

你的Java并发程序Bug,100%是这几个原因造成的

并发解决机制

(编辑:核心网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

热点阅读