1. 共享变量的可见性

线程通信主要是通过对共享变量的读写来进行的,一般共享变量,我们会采用使共享变量不可变或者在任何访问状态变量的时候使用同步两种措施进行变量共享。

2. 使用锁同步共享变量状态

2.1 synchronized(内置锁)

synchronized我们知道常常用来临界区互斥执行,但是除此之外,它还有最重要的功能:锁的内存语义。

1
2
3
4
5
6
7
8
9
class MonitorExample{
int a = 0;
public synchronized void writer(){// 1
a++; // 2
} // 3
public synchronized void reader(){ // 4
int i=a; // 5
} // 6
}

假设线程A执行writer()方法,随后线程B执行reader()方法。根据JMM的happens-before规则,这段代码包含的happens-before关系可以分为3类。

1)根据程序次序规则,1 happens-before2, 2 happens-before 3; 4 happens-before 5, 5 happens-before 6。

2) 根据监视器锁规则,3 happens-before 4。
3) 根据happens-before的传递性,2 happens-before 5。

注:happens-before规则保证了线程A执行后的结果对B可见,但是线程A的代码不一定在线程B之前执行。上面A与B线程是有先后顺序的,主要是为了方便解释锁的内存语义。

happens-beofre
图片来自Java并发编程艺术图3-24

锁释放和锁获取的内存语义:

线程A释放一个锁,实质上是线程A向接下来将要获取的这个锁的某个线程发出了(线程A对共享变量所做修改的)消息。

线程B释放一个锁,实质上是线程B接收了之前某个线程发出的(释放这个锁之前对共享变量所做的修改的)消息。

线程A释放锁,线程B获取锁,这个过程实质上是线程A通过主内存向线程B发送消息。

lock-acquire-state
图片来自Java并发编程艺术图3-26

3. 使用同步原语

3.1 volatile

volatile我们熟知的特性是:

1.可见性

对一个volatile变量的读,总能看到任意线程对这个volatile变量最后的写入。

2.有序性

如果程序是在多处理器上运行,就为cmpxchg指令加上lock前缀。lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成。

3.2 final

引用

Java并发编程实践
Java并发编程艺术