Java并发编程的艺术-第三章之Java内存模型

来源:互联网 发布:全国地区数据库 编辑:程序博客网 时间:2024/05/16 12:30

并发编程模型的关键问题

在并发编程中,需要处理2个关键的问题:线程间如何通信和线程之间如何同步。
线程之间的同通信机制有2种:共享内存和消息传递。
同步:程序中用于控制不同线程间操作发生相对顺序的机制。
       Java并发采用的是共享内存的模型,同步是显示进行的,就是程序必须显示指定(用synchronized、volatile、final)某个方法或某段代码需要在线程之间是互斥的。Java 线程之间通信有Java内存模型(JMM)控制,JMM通过控制主内存与每个线程的本地内存之间的交互,来为保证内存可见性。

重排序

重排序是指编译器和处理器为了优化程序的性能而对指令序列进行重新排序的一种手段。重排序不会改变单线程的执行结果,但是可以会改变多线程的执行结果。

顺序一致性

如果程序是正确同步的,程序的执行结果具有顺序一致性,即,程序的执行结果与其在顺序一致性模型中执行的结果是一致的。

常用的同步原语包括synchronized、volatile、final,下面主要介绍它们的原理

Volatile

Volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。
使用建议:在两个或者更多的线程访问的成员变量上使用volatile。当要访问的变量已在synchronized代码块中,或者为常量时,不必使用。
由于使用volatile屏蔽掉了VM中必要的代码优化,所以在效率上比较低,因此一定在必要时才使用此关键字。 
先写出volatile特点:
可见性:对于一个volatile变量的读,总能看到任意线程对这个volatile变量最后的写入。
原子性:对于任意单个volatile变量的读写具有原子性,但是类似于volatile++这种复合操作不具备原子性。
有序性:主要是禁止指令重排序。
适应场景:单个线程写,多个线程读。提供一种比锁更轻量级的通信机制。在功能上锁比volatile功能更强大,可提供整个临界区代码的执行具有原子性。在执行性能上,volatile更有优势。所以如果想用volatile替代锁,要注意场景:1)对变量的写操作不依赖于当前值,2)该变量没有包含在具有其他变量的不变式中。其实就是为了保证操作是原子性的。

Synchronized(锁)

锁是Java并发编程中最重要的同步机制,锁可以让临界区互斥,还可以让释放锁的线程向获取同一个锁的线程发送消息。从内存语义上看:释放锁和volatile的写相同、获取锁和volatile的读相同。都是通过主内存发送消息相互通信的。


final

与锁和volatile相比,对final域的读写更像是普通变量的访问,下面介绍final域的内存语义:
对于final域,编译器和处理器要遵循两个重排序规则:
1、在构造函数内对一个final域的写入,与随后把这个被构造函数对象的引用赋值给一个引用变量,这两个操作之间不能重排序;
这样可以保证对象引用为任意线程可见之前,保证对象的final域已经被正确的初始化了,而普通的域没有这个保障。

2、初次读一个包含final域的对象的引用,与随后初次读这个fina域,这两个操作不能重排序。
这样可以保证在读一个对象的final域之前,一定会先读包含这个对象的引用,防止在读普通的域时,这个域还没有被写线程写入。



阅读全文
0 0