Java内存模型和并发机制
内存模型 (memory model)
内存模型描述的是程序中各变量(实例域、静态域和数组元素)之间的关系,以及在实际计算机系统中将变量存储到内存和从内存取出变量这样的低层细节.
不同平台间的处理器架构将直接影响内存模型的结构.
在C或C++中, 可以利用不同操作平台下的内存模型来编写并发程序. 但是, 这带给开发人员的是, 更高的学习成本.
相比之下, java利用了自身虚拟机的优势, 使内存模型不束缚于具体的处理器架构, 真正实现了跨平台.
(针对hotspot jvm, jrockit等不同的jvm, 内存模型也会不相同)
内存模型的特征:
a, Visibility 可视性 (多核,多线程间数据的共享)
b, Ordering 有序性 (对内存进行的操作应该是有序的)
java 内存模型 ( java memory model )
根据Java Language Specification中的说明, jvm系统中存在一个主内存(Main Memory或Java Heap Memory),Java中所有变量都储存在主存中,对于所有线程都是共享的。
每条线程都有自己的工作内存(Working Memory),工作内存中保存的是主存中某些变量的拷贝,线程对所有变量的操作都是在工作内存中进行,线程之间无法相互直接访问,变量传递均需要通过主存完成。
其中, 工作内存里的变量, 在多核处理器下, 将大部分储存于处理器高速缓存中, 高速缓存在不经过内存时, 也是不可见的.
jmm怎么体现 可视性(Visibility) ?
在jmm中, 通过并发线程修改变量值, 必须将线程变量同步回主存后, 其他线程才能访问到.
jmm怎么体现 有序性(Ordering) ?
通过java提供的同步机制或volatile关键字, 来保证内存的访问顺序.
这里讲的是关于Java并发机制的基础模块及如何设计合理的并发机制的抽象思维和设计模式。
有这么几个知识点:
1 “先行发生”的次序(happens-before ordering)
2 “volatile”修饰符的使用
3 线程安全的延迟初始化
4 “Final”字段
5 关于Java并发机制的一些建议
happens-before ordering
当我们在Java里谈起互斥锁定(mutual-exclusion lock)时,通常都指当我首先进入了一个互斥锁定,其他人试图获得这个同样的互斥锁定时,必须在我释放了之后才可以。这是Java或C++里关于互斥锁定的最重要的属性。但事实上,这不是互斥锁定唯一的属性。还有一个属性是可见性(visibility),它和次序属性(ordering)紧密相关。
当一个线程使用一个锁定时,它决定了其他线程何时可以看到该线程在锁定后所做的更新。当一个线程对一个变量进行写的操作时,这个写的操作是否会被其他线程看到将取决于该线程使用的是何种锁定。
下面是一个小测验。有下面这段程序:
x=y=0;
//now start threads
//thread 1
x=1;
j=y;
//thread 2
y=1;
i=x;
问题是,在线程1和2执行完毕后,有没有可能i和j都等于0?
我们知道,如果i和j结果都为0的话,对y的读(在j=y里用到)一定比对y的写先发生,类似地,对x的读一定比对x的写先发生?那么,这可能吗?
答案是肯定的。事实上,编译器和处理器都可能对上述程序重新排序,尤其在使用多个处理器,赋值并没有在主内存里同步时。现代的java内存模型使上述现象成为可能。上面的程序显然是错误的未经同步的代码,因为它没有使用锁定。当不同的线程需要读写同一个数据时,必须使用锁定的技术。
再看看下面一段非常关键的代码。可以说,这段代码是全篇演讲的核心。
thread 1:
ref1.x = 1;
lock M;
glo = ref1;
unlock M;
thread 2:
lock M;
ref2 = glo;
unlock M;
j = ref2.x;
thread1里有几个写的操作,在对glo变量进行写的操作之前,它首先对对象M进行了锁定。在thread2里,当thread1释放了对M的锁定之后,它过得了对M的锁定,并开始对glo的读操作。问题是,在thread1里的写操作,thread2进行读操作时,可以看到吗?
答案是肯定的,原因是thread1里对M对象的释放和thread2里对同一个对象M的获得,形成了一个配对。可以这样想,当M在thread1里被释放后,在thread1里所作的更新就被推出(到主内存),随后的在thread2里对M的获得,就会抓取所有在thread1里所作的更新。作为thread2能得到在thread1里的更新,这就是happens-before的次序。
一个释放的操作和相匹配的之后发生的获得操作就会建立起业已发生的次序。在同一个线程里的执行次序也会建立起业已发生的次序(后有例子会涉及到在同一线程里的执行次序问题)。 业已发生的次序是可以转换的。
如果同时有两笔对同一个内存地址的访问,其中一笔是写的操作,并且内存地址不是volatile的,那么这两笔访问在VM里的执行次序就会按照“先行发生”的规则来排。
下面举一些例子来说明问题。请看下面的程序:
- oyxz's blog
- 6473 次点击

评论
发表新评论