在讲解synchronized、volatile、CAS的区别之前,需要弄明白什么共享资源、什么是线程安全、线程间的可见性问题以及Java操作的原子性。弄明白这些问题可以加深你对synchronized、volatile、CAS的理解。下面我会先对这些问题进行简单的说明,
什么是资源共享?
同一个资源被多个线程所持有 或者说多个线程可以访问该资源,这样的资源称之为共享资源。
什么线程安全?
就是指多个线程同时读写同一个资源,并没有使用任何的同步措施,导致出现脏数据或者不可预见的结果。
注:如果多个线程只是访问共享资源,而不去修改的话,是不存在线程安全问题的。只有当线程修改共享资源的时候,才会出现线程安全问题。
线程间的可见性问题
java内存模型规定,所有的变量都存放在主内存中,当线程使用变量的时候,会把主内存中的变量赋值到自己的工作内存中,线程读写变量时,操作的是线程自己内存中变量。
进一步解读:
1、变量(也就是共享资源)存储在主内存中
2、线程使用变量的时候,会在自己的工作内存保存该变量的副本。
3、线程在工作内存中修改副本,并把修改后的变量副本同步到主内存中
4、主内存中的变量已经发生了改变,但不会将修改后的变量同步给其他线程,其他线程的工作空间中的副本变量的值还是修改前的值,也就造成了线程间资源的同步问题
这也就是线程间的不可见性
java操作的原子性
所谓的原子性操作,就是指执行一系列操作的时候,这些操作要么全部执行,要么全部不执行,不存在指执行一部分的情况。
在一些面试中,++i是原子性操作吗?通过一个示例简单的计数器进行说明,代码如下:
public class ThreadNotSafeCount {
private int count;
public int getCount() {
return count;
}
public synchronized void inc(){
++count;
}
}
使用过javap -c查看汇编代码,如下所示:
其中,简单的++count由2、5、6、7四步组成,也就是红框中所标识的汇编代码,其中第2步是获取当前count的值并放入栈顶,第5步是把常量1放入栈顶,第6步是把放入栈顶的两个值相加并把结果放入栈顶,第7步是把栈顶的结果赋值给count。由此可以发现++count,包含了count的读--改--写过程,所以++count不是一个原子性操作。
线程间不可见性的解决方案
synchronized锁机制解决线程间的可见性问题
线程synchronized加锁时,其他线程会进入阻塞,而该线程会清空锁块内线程工作内存中的变量,在使用这些变量的时候,会从主内存中加载。synchronized在释放锁时,会将线程工作空间中的变量同步到主内存中。这样也就解决了线程间的可见性问题。
volatile关键字解决线程间的可见性问题
当变量使用volatile关键字修饰变量,线程在使用该变量的时候会去主内存中读取,修改后,会把值同步给主内存。这跟synchronized有点类似。
synchronized锁机制和volatile关键字解决线程间的可见性问题的区别
使用synchronized锁机制,当线程获得锁之后,其他线程的调用会被阻塞,同时也会增加线程上下文切换和线程重新调度的开销。
使用volatile关键字,是非阻塞式解决方案,不会增加线程的上下文切换以及线程重新调度的开销。
如何确保java的原子性操作?
sychronized锁机制,当线程进入synchronized代码块前,获得锁,其他线程就会被阻塞挂起。当该线程正常退出或者抛出异常或者调用wait()系列方法释放锁时,其他线程才有机会获得锁,执行该代码块。简而言之,sychronized锁机制通过阻塞来确保java操作的原子性。
volatile关键字并不会保证操作的原子性。
小小总结
synchronized能解决线程之间的可见性问题和确保java操作的原子性,但是会做成线程阻塞,增加线程上下文切换和线程重新调到的开销问题、
volatile能通过非阻塞式的方式解决了线程间的可见性问题,但不能保证java操作的原子性。也不会增加线程切换和线程重新调度的开销问题。
什么是CAS?
CAS就是Compare And Swap,是JDK提供给的非阻塞性原子性操作。它通过硬件保证了比较--更新的原子性操作。JDK中的Unsafe提供了一些列的CAS方法,Unsafe类中的方法都是native方法,它们通过使用JNI的方式访问本地的C++实现库。
下面就compareAndSwapLong()方法进行简单介绍
public final native boolean compareAndSwapLong(Object Obj, long valueOffset, long expect, long update);
其中compareAndSwap的意思就是比较交换,CAS有四个操作数,分别为:对象内位置,对象中的变量的偏移量,变量的预期值和新的值。其操作含义是,如果obj中的内存偏移量为valueOffset,则使用新的值update替换旧的值expect。这是处理器提供的一个原子性指令
参考:《java并发编程之美》