本文将结合源码分析锁的底层实现机制,对于基本的使用不介绍
锁的内存语义
众所周知,锁可以让临界区互斥执行。在这里将介绍锁的另一个功能:锁的内存语义
锁是java中并发编程中非常重要的同步机制,锁除了临界区的互斥执行,还可以完成线程间的通信,如下代码
class MonitorExample{
int a = 0;
public synchronized void writer(){//1
a++;//2
}//3
public synchronized void reader(){//4
System.out.println(a);//5
}//6
}
根据我们的happens-before(以下简称HB)规则,这个过程的happens-before关系可以分为三类
- 根据程序次序规则 1 HB 2 ,2 HB 3;4 HB 5,5 HB 6
- 根据监视器锁规则 3 HB 4
- 根据传递性:2 HB 5
因此,如果线程A获取锁执行writer后释放锁,线程B执行reader能够得到正确的值,保证了共享变量的可见性
锁的释放与获取内存语义
当线程释放锁时,JMM会把该线程本地内存的共享变量刷新到主存,线程获取 锁时,JMM会将本地缓存置为无效,使得临界区的代码需要从主存重新读取
锁内存语义的实现
这里以ReentrantLock的源码为例分析实现机制,使用lock方法加锁,unLock释放锁,深入源码,我们会发现锁的内部其实维护了一个队列同步器(AQS),如下类图:
在AQS的内部维护了一个整形的volatile变量,ReentrantLock分为公平锁与非公平锁,先看下公平锁,看下调用轨迹
加锁调用轨迹,ReentrantLock.lock(), FairSync.lock() AbstactQueueSynchonized.acquire(int arg),ReentrantLock.tryAcquire(int acquires),这一步开始真正的获取锁:加锁方法首先读取volatile变量state,释放锁是写volatile变量state
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
其中有行代码compareAndSetState,有没有感觉很熟悉,compareAndSet不就是我们使用的CAS操作嘛,这里采用CAS来保证以原子的方式更新state变量
最后我们可以看下整个并发包都是在CAS与volatile变量的基础上建立的