这里整理下Java并发中的基本概念,以及个人的理解。
并发与并行
并行,指的是一个工作者同时做多件事情;并发指的是同一时刻多个工作者做多件事情。
对应到系统,比如说单个CPU,并行就是指单个CPU同时执行多个线程,在同一时间点,只会执行一个线程;并发指的是多个CPU同时执行多个线程。并发一定是并行的,并行的不一定是并发的。
内存可见性
这个概念初看,很容易就产生个疑问:难道内存还有不同见的情况?的确会有,需要了解这个问题,那就需要先了解下Java的内存模型:
线程、主内存、工作内存之间的关系如上图所示。 工作内存与主内存之间如果不同步,就会造成内存可见性问题: 如果线程A对共享变量X进行了修改,但是线程A没有及时把更新后的值刷入到主内存中,而此时线程B从主内存读取共享变量X的值,所以X的值是原始值,那么我们就说对于线程B来讲,共享变量X的更改对线程B是不可见的。
指令重排序
指令重排序包含两个方面:编译器重排与运行期重排。
编译期重排。编译源代码时,编译器依据对上下文的分析,对指令进行重排序,以之更适合于CPU的并行执行。
运行期重排,CPU在执行过程中,动态分析依赖部件的效能,对指令做重排序优化。
单看指令重排序这个概念,如果没有一定的限制,很明显指令重排序会使程序陷入混乱,没有办法理解,那怎么去限制指令重排序的范围呢,这个就要看下happens-before了。happens-before是在Java内存模型中定义好的, 在以下八种情况下,通过增加内存屏障指令,限制了指令重排序的范围:
同一个线程中的每个Action都happens-before于出现在其后的任何一个Action。
对一个监视器的解锁happens-before于每一个后续对同一个监视器的加锁。
对volatile字段的写入操作happens-before于每一个后续的同一个字段的读操作。
Thread.start()的调用会happens-before于启动线程里面的动作。
Thread中的所有动作都happens-before于其他线程检查到此线程结束或者Thread.join()中返回或者Thread.isAlive()==false。
一个线程A调用另一个另一个线程B的interrupt()都happens-before于线程A发现B被A中断(B抛出异常或者A检测到B的isInterrupted()或者interrupted())。
一个对象构造函数的结束happens-before与该对象的finalizer的开始
如果A动作happens-before于B动作,而B动作happens-before与C动作,那么A动作happens-before于C动作。
volatile
可以使用volatile变量来解决内存可见性的问题,volatile描述符的主要作用:
保证被volatile修饰的共享gong’x变量对所有线程总数可见的,也就是当一个线程修改了一个被volatile修饰共享变量的值,新值总数可以被其他线程立即得知。
禁止指令重排序优化。
不能保证volatile变量符合操作的原子性。
原子性
这个概念在数据库里也有,一个操作要确保原子性,这个操作不能出现中间状态,要么操作全部成功,要么操作全部失败。比如a++,这个操作实际内部需要“读a->计算a+1->设置a”。要确保这个操作是原子性的,其它线程在对a进行操作时,只能在a++之前或之后对a进行操作,而不能在读 a之后或者设置a之前对a进行操作。
参考文档