如未作特殊说明,文章均为原创,转发请注明出处。
[TOC]
前言
上篇文章详细分析了volatile
的内存语义,并且介绍了是如何实现线程之间通信,可见性。保证单个操作下的volatile
变量的原子性。并且将其的内存语义与锁做了对比。那么本章就来详细的分析锁的内存语义。
锁的内存语义
一提到锁,大家自然而然地想到前面分析过地synchronized
关键字。 它是JAVA
并发编程中最重要地同步机制。锁可以让临界区(同步块)互斥(独占锁)。下面来分析以下锁地另一个同样非常重要地锁地内存语义。
锁地释放-获取建立的happens-before关系
通过一段代码来具体地分析以下锁地释放和获取建立的happens-before
关系。
public class MonitorExample{
int a = 0;
public synchronized void write(){ // 1
a++; // 2
} // 3
public synchronized void read(){ // 4
int i = a; // 5
} // 6
}
假设线程A执行write()
线程B执行read()
,两个方法都使用synchronized
进行同步,并且锁对象是同样的(两个线程同一把锁),那么很显然,如果线程A先执行,那么线程B只能等到线程A执行完之后释放锁,获取锁之后才能真正的执行read()
。
那么可以得出结论是:
- 1
happens-before
2 - 2
happens-before
3 - 3
happens-before
4 - 4
happens-before
5 - 5
happens-before
6
根据锁的获取跟释放关系 : 3 happens-before
4
根据happens-before
的传递性: 2 happens-before
5
这里可以推测出,当线程A释放锁的同时,所有锁内的共享变量将对线程B可见。(类似于volatile
的内存语义)这种操作也可以看作两个不同的线程通过主内存来通信。实现共享变量在多线程下的原子性。
锁的释放-获取的内存语义
当线程A释放锁时,会将修改后的变量刷新到主内存,那B线程在拿到锁后会将本地内存缓存的值置为无效,那么在临界代码块中的共享变量就必须要从主内存中重新获取。

在线程A执行完后,所有等待这把锁的线程的本地内存缓存的数据都会被置为无效,在线程B执行的时候,必须重新在主内存中读取。

这样看来
锁的释放-获取内存语义
跟volatile的写-读内存语义
有异曲同工之处。
下面给锁的获取-释放内存语义做个小的总结
锁的释放-获取内存语义小结
- 线程A在释放一个锁,实质上时线程A向接下来要获取该锁的线程发出了(线程A对共享变量所做修改的)消息。
- 线程B获取一个锁,实质上是线程B接收了之前某个线程发出的(在释放这个锁之前对共享变量所做的修改的)消息。所以获取这个锁之后,所有的共享变量必须重新到主内存中读取。
- 线程A释放锁,随后线程B获取这个锁,这个过程实质上是线程A通过主内存向线程B发送消息。
锁内存语义的实现
这里等深入了解了AQS
java同步框架AbstractQueuedSynchronizer。
网友评论