【Android每日一问】synchronized和volatile区别

272 阅读4分钟

先了解一下Java内存模型中的原子性,可见性,有序性:


Java内存模型中的原子性,可见性,有序性

  • 原子性 指一个操作是按原子的方式执行的。要么该操作不被执行;要么以原子方式执行,即执行过程中不会被其它线程中断。

  • 可见性 可见性,是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的。在java中可以实现可见性的两个关键字synchronized,volatile

  • 有序性 如果在本线程内观察,所有的操作都是有序的:如果在一个线程中观察另外一个线程,所有的线程操作都是无序的。前半句是指“线程内表现为串行的语义”,后半句是指“指令重排序”现象和“工作内存与主内存同步延迟”现象。Java语言提供了volatile和synchronized两个关键字来保证线程之间操作的有序性


Synchronized能够实现多线程的原子性(同步)和可见性。

JVM关于Synchronized的两条规定:

  • 线程解锁前,必须把共享变量的最新值刷新到主内存中。

  • 线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新读取最新的值(注意:加锁和解锁需要同一把锁)。

Synchronized执行互斥代码的过程

  • 获得互斥锁

  • 清空工作内存

  • 从主内存拷贝变量的最新副本到工作内存

  • 执行代码

  • 将更改后的共享变量的值刷新到主内存

  • 释放互斥锁

volatile可以保证变量的可见性,但是不能保证复合操作的原子性

volatile如何实现内存可见性:

深入来说:通过加入内存屏障禁止重排序优化来实现的

  • 对volatile变量执行写操作时,会在写操作后加入一条store屏障指令.
  • 对volatile变量执行读操作时,会在读操作前加入一条load屏障指令.
通俗地讲:volatile变量在每次被线程访问时,都强迫从主内存中重读该变量的值,
而当该变量发生变化时,又会强迫线程将最新的值刷新到主内存,
这样任何时刻,不同的线程总能看到该变量的最新值。

线程写volatile变量的过程:

  • 改变线程工作内存中volatile变量副本的值
  • 将改变后的副本的值从工作内存刷新到主内存

线程读volatile变量的过程:

  • 从主内存中读取volatile变量的最新值到线程的工作内存中
  • 从工作内存中读取volatile变量的副本

volatile适合的使用场景

只能在有限的一些情形下使用 volatile 变量替代锁。要使 volatile 变量提供理想的线程安全,必须同时满足下面两个条件:

  • 对变量的写入操作不依赖其当前值
  • 该变量没有包含在具有其他变量的不变式中。

第一个条件的限制使 volatile 变量不能用作线程安全计数器。虽然增量操作(x++)看上去类似一个单独操作,实际上它是一个由(读取-修改-写入)操作序列组成的组合操作,必须以原子方式执行,而 volatile 不能提供必须的原子特性。实现正确的操作需要使x 的值在操作期间保持不变,而 volatile 变量无法实现这点。(然而,如果只从单个线程写入,那么可以忽略第一个条件。)

总结:

  • volatile比synchronized更轻量级。

  • volatile没有synchronized使用的广泛。

  • volatile不需要加锁,比synchronized更轻量级,不会阻塞线程。

  • 从内存可见性角度看,volatile读相当于加锁,volatile写相当于解锁。

  • synchronized既能保证可见性,又能保证原子性,而volatile只能保证可见性,无法保证原子性。

  • volatile本身不保证获取和设置操作的原子性,仅仅保持修改的可见性。但是java的内存模型保证声明为volatile的long和double变量的get和set操作是原子的。