1. 共享变量的可见性

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

1.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

1.2 volatile

volatile我们熟知的特性是:

1.可见性

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

2.有序性

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

1.3 final

2. 等待通知(wait/notify)

3. 管道通信

4. Thread.join()

5. ThreadLocal

引用

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