并发编程的三个重要特性
| |
---|
原子性 | 所谓原子性是指在一次的操作或者多次操作中,要么所有的操作全部都得到了执行并且不会受到任何因素的干扰而中断,要么所有的操作都不执行。 |
可见性 | 可见性是指,当一个线程对共享变量进行了修改,那么另外的线程可以立即看到修改后的最新值。 |
有序性 | 所谓有序性是指程序代码在执行过程中的先后顺序,由于Java在编译器以及运行期的优化,导致了代码的执行顺序未必就是开发者编写代码时的顺序。 |
JMM如何保证三大特性
JMM与原子性
原子性操作 | 非原子 |
---|
对基本数据类型的变量读取赋值操作都是原子性的 | 多个原子性的操作在一起就不再是原子性操作 |
对引用类型的变量读取和赋值的操作也是原子性的 | y++ |
get i | y=x |
i+1 | z=z+1 |
set i → x=10 | |
Java如何保证原子性性 |
---|
JMM只保证了基本读取和赋值的原子性操作,其他的均不保证 |
使用关键字synchronized |
使用JUC中的lock |
如果想要使得int等类型自增操作具备原子性,可以使用JUC包下的原子封装类型java.util.concurrent.atomic .* |
JMM与可见性
在多线程的环境下,如果某个线程首次读取共享变量,则首先到主内存中获取该变量,然后存入工作内存中,以后只需要在工作内存中读取该变量即可。 |
---|
同样如果对该变量执行了修改的操作,则先将新值写入工作内存中,然后再刷新至主内存中。但是什么时候最新的值会被刷新至主内存中是不太确定的。 |
Java提供了以下三种方式来保证可见性 | |
---|
使用关键字volatile | 当一个变量被volatile关键字修饰时,对于共享资源的读操作会直接在主内存中进行(当然也会缓存到工作内存中,当其他线程对该共享资源进行了修改,则会导致当前线程在工作内存中的共享资源失效,所以必须从主内存中再次获取),对于共享资源的写操作当然是先要修改工作内存,但是修改结束后会立刻将其刷新到主内存中 |
| 1)Reader线程从主内存中获取init_value的值为0,并且将其缓存到本地工作内存中。 2)Updater线程将init_value的值在本地工作内存中修改为1,然后立即刷新至主内存中。 3)Reader线程在本地工作内存中的init_value失效(反映到硬件上就是CPU的L1或者L2的Cache Line失效)。 4)由于Reader线程工作内存中的init_value失效,因此需要到主内存中重新读取init_value的值。 |
| |
通过synchronized 关键字能够保证可见性 | synchronized关键字能够保证同一时刻只有一个线程获得锁,然后执行同步方法,并且还会确保在锁释放之前,会将对变量的修改刷新到主内存当中。 |
| |
通过JUC提供的显式锁Lock 也能够保证可见性 | Lock的lock方法能够保证在同一时刻只有一个线程获得锁然后执行同步方法,并且会确保在锁释放(Lock的unlock方法)之前会将对变量的修改刷新到主内存当中。 |
JMM与有序性
在Java的内存模型中,允许编译器和处理器对指令进行重排序,在单线程的情况下,重排序并不会引起什么问题 |
---|
但是在多线程的情况下,重排序会影响到程序的正确运行 |
Java提供了三种保证有序性的方式 | |
---|
使用volatile 关键字来保证有序性 | volatile关键字对顺序性的保证就比较霸道一点,直接禁止JVM和处理器对volatile关键字修饰的指令重排序,但是对于volatile前后无依赖关系的指令则可以随便怎么排序 |
使用synchronized 关键字来保证有序性 | 同步代码在执行的时候与在单线程情况下一样自然能够保证顺序性(最终结果的顺序性) |
使用显式锁Lock 来保证有序性 | 同步代码在执行的时候与在单线程情况下一样自然能够保证顺序性(最终结果的顺序性) |
-----------------------------------------------------------------------------读书笔记摘自书名:Java高并发编程详解:多线程与架构设计 作者:汪文君