同步语义-volatile
volatile是啥
volatile,在Java中保证了线程之间对于共享变量的同步,这里的同步,是指内存可见性,除去保证内存可见性,volatile语义还对JMM对于指令的重排序进行了禁止。
提到volatile的同步,在线程中通信机制中,有两种方式来完成线程的通信:共享内存和消息传递(actor)。
共享内存:
是通过设置一个共享变量,多个线程来对这个共享变量进行读写操作,在读写过程中,不同的线程读取到的数据可能是其他线程写入的,这样就做到了隐式通信,同时需要指定方法或者变量之间的锁的关系来使得线程完成数据操作,这样做到了显示同步。
消息传递
消息传递是通过两个线程之间的消息队列,也就是线程A对线程B直接发起的消息通信,来完成信息传递。在这个过程中,线程之间没有公共状态,必须通过发送消息来显式进行通信,同时因为发送消息总是能够在接收消息之前,做到了隐式同步。
并发模型 | 通信机制 | 同步机制 |
---|---|---|
共享内存 | 线程之间共享公共状态,通过写-读内存中的公共状态进行隐式通信。 | 必须指定线程的互斥执行顺序,做到显式同步。 |
消息传递 | 线程没有公共状态,所以必须通过发送消息来进行显式通信。 | 消息的发送必须在消息的接收之前,所以进行隐式的同步。 |
volatile的作用及实现
volatile能够对一个变量的读-写操作进行同步,对于单个变量的读-写操作,能够看成是使用同一个锁对读-写操作进行了同步。
volatile特性:
可见性:对一个volatile变量的读,总是能够看到(任意线程)对这个volatile变量最后的写入。
原子性:对单个volatile变量的读/写操作具有原子性,但是对于复合操作不具有原子性,哪怕这个复合操作是基于volatile变量。
volatile通过JSR-133定义的happens-before关系,来实现在两个线程之间的通信。
happens-before关系
如果一个操作执行的结果对另一个操作可见,那么这两个操作之间必须存在happens-before关系。这两个操作可以是在同一个线程之内,也可以在多个线程之间。
happens-before规则
程序顺序规则:一个线程中的每个操作,happens-before于该线程中的任意后续操作。
监视器锁规则:对一个锁的解锁,happens-before于随后对于这个锁的加锁。
volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读。
传递性:如果A happens-before B,且B happens-before C,那么A happens-before C。as-if-serial 语义
as-if-serial语义是指:不管怎么重排序,(单线程)的执行结果不能被改变。
在写入一个共享volatile变量时,JMM会把该新城对应的本地内存中的共享变量值刷新到主内存;在读一个volatile变量时,JMM会把该线程对应的本地内存设置为无效,线程会从主内存中读取共享变量。通过对volatile变量写之后的写入主内存操作和读之前的从主内存读取操作,保证了可见性。
volatile通过在JMM(Java Momery Modal)层次插入内存屏障,来做到对指令重排序的禁止。
在JMM中,volatile对于内存屏障的实现是悲观的:
每次写操作前插入StoreStore屏障,写操作后插入StoreLoad屏障
每次读操作前插入LoadLoad屏障,读操作后插入LoadStore屏障
满足下列条件,volatile可以使用:
对变量的写入不依赖变量的当前值
变量不包含在具有其他变量的不变式中
volatile为什么不能保证原子性
尽管volatile在内存可见性方面表现出了锁的状态,但是volatile并不是锁,所以不能够提供原子性。
在上面的这个程序中,如果在单线程中执行,那么这个操作将不会有错误,但是当放到并发环境中进行执行的时候,哪怕变量count是volatile变量,在写入之后能够将数据刷回主内存,但是这个程序依然是存在危险的。问题的原理同共享内存模型中不能够保持原子性的原理,即++操作属于复合操作,并非原子操作。
volatile跟synchronized的差别
synchronized实现可见性的时候,在线程角度执行互斥代码需要做到:
获得互斥锁
清空工作内存
拷贝变量的最新副本到工作内存
执行代码
将更改后的变量的值刷新到主内存
释放锁
volatile不是锁,但是volatile提供了原子变量在内存中的可见性,在并发设计中,这个操作将能够大大节省系统的开销。对于volatile的读,相当于synchronized的加锁,volatile的写,相当于synchronized的解锁,但是volatile不会阻塞线程,响应速度更快。
volatile本质上是工作内存中的值不确定的,需要从主内存中重新读取;而synchronized本质是使用锁将变量锁定,只有当前变量可以访问,其他变量将被阻塞。
volatile只是存在于变量级别,而synchronized存在于变量,方法和类级别。
volatile只能怪保证内存可见性,不能保证原子性操作;而synchronized可以保证原子性和可见性。
关于volatile还需要知道什么
volatile对于64位的Long类型变量有着优化。
这一点需要从处理器总线的工作机制说起,在一些32位的处理器上,JVM运行时可能会把一个64位long/double类型的变量的写操作拆分为两个32位的写操作,并且将这两个32位的写操作分配到不同的总线事务中执行,此时这个64位变量的写操作不再具有原子性。同样的,读64位操作也是有可能是非原子的。通过将一个long/double变量声明为volatile的,JMM保证这些变量的set和get操作是原子的。