Java并发之原子性、有序性、可见性

​ 原子性指的是一个或者多个操作在 CPU 执行的过程中不被中断的特性

线程切换 带来的原子性问题

Java 并发程序都是基于多线程的,操作系统为了充分利用CPU的资源,将CPU分成若干个时间片,在多线程环境下,线程会被操作系统调度进行任务切换。

Java并发之原子性、有序性、可见性

为了直观的了解什么是原子性,我们看下下面哪些操作是原子性操作

int count = 1; //1
count++;       //2
int a = count; //3
复制代码

上面展示语句中,除了语句1是原子操作,其它两个语句都不是原子性操作,下面我们来分析一下语句2

其实语句2在执行的时候,包含三个指令操作

  • 指令 1:首先,需要把变量 count 从内存加载到 CPU的寄存器
  • 指令 2:之后,在寄存器中执行 +1 操作;
  • 指令 3:最后,将结果写入内存

对于上面的三条指令来说,如果线程 A 在指令 1 执行完后做线程切换,线程 A 和线程 B 按照下图的序列执行,那么我们会发现两个线程都执行了 count+=1 的操作,但是得到的结果不是我们期望的 2,而是 1。

Java并发之原子性、有序性、可见性

操作系统做任务切换,可以发生在任何一条CPU 指令执行完

有序性

​ 有序性指的是程序按照代码的先后顺序执行

**编译优化 **带来的有序性问题

为了性能优化,编译器和处理器会进行指令重排序,有时候会改变程序中语句的先后顺序,比如程序:

a = 5;     //1
b = 20;    //2
c = a + b; //3
复制代码

编译器优化后可能变成

b = 20;    //1
a = 5;     //2
c = a + b; //3
复制代码

在这个例子中,编译器调整了语句的顺序,但是不影响程序的最终结果

synchronized(具有有序性、原子性、可见性)表示在同一时刻只能由一个线程进行获取,当锁被占用后,其他线程只能等待。

在单例模式的实现上有一种双重检验锁定的方式(Double-checked Locking)

public class Singleton {
  static Singleton instance;
  static Singleton getInstance(){
    if (instance == null) {
      synchronized(Singleton.class) {
        if (instance == null)
          instance = new Singleton();
        }
    }
    return instance;
  }
}
复制代码

我们先看 instance = new Singleton() 的未被编译器优化的操作

  • 指令 1:分配一块内存 M;

  • 指令 2:在内存 M 上初始化 Singleton 对象;

  • 指令 3:然后 M 的地址赋值给 instance 变量。

编译器优化后的操作指令

  • 指令 1:分配一块内存 M;

  • 指令 2:将 M 的地址赋值给 instance 变量;

  • 指令 3:然后在内存 M 上初始化 Singleton 对象。

现在有A,B两个线程,我们假设线程A先执行 getInstance() 方法,当执行编译器优化后的操作指令 2 时(此时候未完成对象的初始化),这时候发生了线程切换,那么线程B进入,刚好执行到第一次判断 instance==null 会发现 instance 不等于 null 了,所以直接返回 instance ,而此时的 instance 是没有初始化过的。

Java并发之原子性、有序性、可见性

现行的比较通用的做法就是采用静态内部类的方式来实现

public class SingletonDemo {
    private SingletonDemo() {
    }
    private static class SingletonDemoHandler{
        private static SingletonDemo instance = new SingletonDemo();
    }
    public static SingletonDemo getInstance() {
        return SingletonDemoHandler.instance;
    }
}
复制代码

可见性

​ 可见性指的是当一个线程修改了共享变量后,其他线程能够立即得知这个修改

缓存 导致的可见性问题

首先我们来看一下Java内存模型(JMM)

Java并发之原子性、有序性、可见性
主内存
独立的工作内存

共享变量可见性的实现原理:

线程1对共享变量的修改要被线程2及时看到的话,要经过如下步骤:

  1. 把工作内存1中更新的变量值刷新到主内存
  2. 把主内存中的变量的值更新到工作内存2中

可以使用 synchronizedvolatilefinal 来保证可见性

欢迎关注公众号

Java并发之原子性、有序性、可见性

原文 

https://juejin.im/post/5c7dfc925188251b8c769f69

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。

PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » Java并发之原子性、有序性、可见性

赞 (0)
分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址