volatile和synchronized的本质区别是什么?
volatile和synchronized的唯一区别是,volatile只确保内存可见性,synchronized也确保内存可见性——并且还确保不同互斥,即同一时间,只能有一个线程获取到锁,其他线程阻塞等待获取锁。
与synchronized的区别?
synchronzied能确保共享数据同一时间只有一个线程可以更新共享数据。
但是volatile不能,因为volatile只能确保单个读或写操作是原子,但是对一个共享数据的修改,往往很多时候,需要依赖共享数据的旧值,比如在旧值之上加1,即先读共享数据的旧值——然后加1——最后写,这里实际上就包含了3个操作,所以volatile不能保证真正的同步。
volatile应用场景?
那volatitle应用场景是啥样的?对共享数据修改的时候,不依赖共享数据(本质是不使用共享数据的旧值),并且右边的变量也要确保线程安全(本质是因为右边的变量相当于是一个读的操作,那么更新共享数据的时候,就包含了2个操作:1、先读右边变量的值 2、再更新共享数据。更新操作是原子,但是多了一个读右边变量的操作,就不是原子了,因为现在包含了2个原子操作)。
参考:https://titanwolf.org/Network/Articles/Article?AID=6109465a-ab8f-4f01-b193-caaf5a1a79d6#gsc.tab=0 //这篇文章的作者就是写java并发编程实战的
示例
示例2-AtomicLong
说明:jdk并发包实现的AtomicLong的数据是volatile,就相当于我们自己定义数据的时候用valotile是一样的,只不过现在是jdk并发包的AtomicLong帮你做了这个事情,但是作用和本质是一样的。
单个读或写是原子操作
为什么单个读或写是原子操作?这个是java规范保证的。
准确的说,非long/double数据,才是原子。为什么?因为long/double是64位,而java规范只保证32位才是原子操作。
volatile的作用?
使得共享数据被一个线程更新的时候,其他线程可以立即读到最新值(即所谓的可见性)。即,没加volatile,可能读不到最新值;加了,就确保可以读到最新值。
syncronized的作用?
确保互斥,即同一时间,只能有一个线程更新共享数据。具体到代码层面就是,比如锁定一个更新共享数据的方法或者代码块,同一时间,只有一个线程可以执行这个方法或者代码块,这样就确保了其他线程获取不到锁,也就不能执行方法或者代码块,也就不能更新共享数据。
可见性呢?synchronized也可以确保可见性,可见性是指,线程A修改了共享数据,线程B可以看到最新数据。这似乎是一句废话,事实也是一句废话——这里主要是涉及到java内存模型的问题。
java内存模型
对Java内存模型的理解,以及其在并发中的应用。
Java内存模型的主要目标: 定义程序中各个变量的访问规则。
Java线程之间的通信由Java内存模型(本文简称为JMM)控制。
所有变量的存储都在主内存,每条线程还都有自己的工作内存,线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作必须在工作内存完成,而不能直接读取主内存中的变量。不同的线程直接无法访问对方工作内存中的变量,线程间变量的传递均需要通过主内存来完成。
线程间通信:
首先,线程A把本地内存A中更新过的共享变量刷新(即写)到主内存中去。 //cpu1(线程1)——缓存1(线程工作内存1)——主内存
然后,线程B到主内存中去读取(即读)线程A之前已更新过的共享变量。//cpu2(线程2)——缓存2(线程工作内存2)——主内存
线程只能和线程工作内存通信,线程工作内存然后又和主内存通信。
不同线程的工作内存,不能互相通信,必须通过主内存。比如,线程1写数据到线程工作内存1,然后线程工作内存写数据到主内存;线程工作内存2从主内存读数据,然后线程2就可以从线程工作内存2读数据了。
架构图
cpu维度-架构图
线程维度-架构图
说明:线程维度的架构图可能不好理解,因为为什么会有这个架构图?为什么是这样子?本质是因为cpu维度的架构图。
而cpu维度的架构图的本质是,现在都是多cpu,每个cpu都有自己的高速缓存,为什么每个cpu都要有自己的高速缓存?不是已经有了内存(即架构图里的主内存)吗?因为高速缓存快啊,高速缓存比内存快,而且高速内存比内存要更小,内存越小越快。
各种存储设备的访问速度如下:
寄存器的速度最快,可以在一个时钟周期内访问,
其次是高速缓存,可以在几个时钟周期内访问,
普通内存可以在几十个或几百个时钟周期内访问。
https://zhuanlan.zhihu.com/p/37749443
内存可见性
内存模型这个东西很虚,也没什么意义,主要是不了解内存模型,就不能理解线程的内存可见性——因为不知道为什么,线程1修改了共享数据,但是线程2却不一定能读到共享数据的最新值。原因就在于内存模型,即上面的cpu维度的架构图里提到的,在cpu/线程和内存之间,还多了一个中介内存(即高速缓存/线程工作内存)。
多了一个中介,就多了一个数据不一致的问题。
那怎么解决数据不一致的问题?synchronized或者volatile,都能保证线程1修改共享数据之后,线程2就可以读到共享数据的最新值。
而synchronized和volatile的唯一区别就是,synchronized还多了一个功能,就是互斥的功能,即同一时间,只有一个线程能获取对象的锁,其他线程只能等待该线程释放锁——本质是同一时间,只能有一个线程执行方法或者代码块:即只能有一个线程,更新共享数据。
参考
https://segmentfault.com/a/1190000020770155
https://www.zhihu.com/question/38816432