1. 三大性质简介
synchronized: 具有原子性,有序性和可见性
;
volatile:具有有序性和可见性
在并发编程中分析线程安全的问题时往往需要切入点,那就是
两大核心
:JMM抽象内存模型以及happens-before规则(在
这篇文章
中已经经过了),三条性质:
原子性,有序性和可见性
。关于
synchronized
和
volatile
已经讨论过了,就想着将并发编程中这两大神器在
原子性,有序性和可见性
上做一个比较,当然这也是面试中的高频考点,值得注意。
2. 原子性
原子性是指
一个操作是不可中断的,要么全部执行成功要么全部执行失败,有着“同生共死”的感觉
。及时在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程所干扰。我们先来看看哪些是原子操作,哪些不是原子操作,有一个直观的印象:
int a = 10; //1a++; //2int b=a; //3a = a+1; //4
上面这四个语句中只
有第1个语句是原子操作
,将10赋值给线程工作内存的变量a,而语句2(a++),实际上包含了三个操作:1. 读取变量a的值;2:对a进行加一的操作;3.将计算后的值再赋值给变量a,而这三个操作无法构成原子操作。对语句3,4的分析同理可得这两条语句不具备原子性。当然,
java内存模型
中定义了8中操作都是原子的,不可再分的。
-
lock(锁定):作用于主内存中的变量,它把一个变量标识为一个线程独占的状态;
-
unlock(解锁):作用于主内存中的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
-
read(读取):作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存中,以便后面的load动作使用;
-
load(载入):作用于工作内存中的变量,它把read操作从主内存中得到的变量值放入工作内存中的变量副本
-
use(使用):作用于工作内存中的变量,它把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作;
-
assign(赋值):作用于工作内存中的变量,它把一个从执行引擎接收到的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作;
-
store(存储):作用于工作内存的变量,它把工作内存中一个变量的值传送给主内存中以便随后的write操作使用;
-
write(操作):作用于主内存的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中。
上面的这些指令操作是相当底层的,可以作为扩展知识面掌握下。那么如何理解这些指令了?比如,把一个变量从主内存中复制到工作内存中就需要执行read,load操作,将工作内存同步到主内存中就需要执行store,write操作。注意的是:
java内存模型只是要求上述两个操作是顺序执行的并不是连续执行的
。也就是说read和load之间可以插入其他指令,store和writer可以插入其他指令。比如对主内存中的a,b进行访问就可以出现这样的操作顺序:
read a,read b, load b,load a
。
由原子性变量操作read,load,use,assign,store,write,可以
大致认为基本数据类型的访问读写具备原子性
(例外就是long和double的非原子性协定)
synchronized
上面一共有八条原子操作,其中六条可以满足基本数据类型的访问读写具备原子性,还剩下lock和unlock两条原子操作。如果我们需要更大范围的原子性操作就可以使用lock和unlock原子操作。尽管jvm没有把lock和unlock开放给我们使用,但jvm以更高层次的指令monitorenter和monitorexit指令开放给我们使用,反应到java代码中就是---synchronized关键字,也就是说
synchronized满足原子性
。
volatile 我们先来看这样一个例子:
public class VolatileExample {
private static volatile int counter = 0;
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
Thread thread = new Thread(new Runnable() {
@Override