从所周知,锁可以让临界区互斥执行。
锁的释放-获取建立的happens-before关系
锁时Java并发编程中最重要的同步机制。锁除了让临界区互斥执行外,还可以让释放锁的线程向获取同一个锁的线程发送消息。
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()方法。
根据happens-before规则,这个 过程包含的happens-before关系可以分为3类。
(1)根据程序次序规则,1 happens-before 2,2 happens-before 3;4 happens-before 5,5 happens- before 6
(2)根据监视器锁规则,3 happens-before 4
(3)根据happens-before的传递性,2 happens-before 5。
在线程A释放了锁之前所有可见的共享变量,在线程B获取同一个锁以后,将立即变得对线程B可见
锁的释放和获取的内存语义
当线程释放锁,JMM会把该线程对应的本地内存中的共享变量刷新到主内存。
当线程获取锁,JMM会把该线程对应的本地内存置为无效。从而使得被监视锁保护的临界区代码必须从主内存中读取共享变量。
锁释放与volatile写又相同的内存语义;锁获取与volatile读有相同的内存语义
总结
(1)线程A释放一个锁,实质上是线程A向接下来将要获取这个锁的某个线程发出了(线程A对共享变量所做修改的)消息
(2)线程B获取一个锁,实质上是线程B接受了之前某个线程发出的(在释放这个锁之前对共享变量所做的修改的)消息
(3)线程A释放锁,随后线程B获取锁,这个过程实质上是线程A通过主内存向线程B发送消息
锁内存语义的实现
class ReentrantLockExample {
int a = 0;
ReentrantLock lock = new ReentrantLock();
public void writer() {
lock.lock(); 获取锁
try {
a++;
} finally {
lock.unlock(); 释放锁
}
}
public void reader() {
lock.lock(); 获取锁
try {
int i = a;
……
}finally {
lock.unlock(); 释放锁
}
}
}
在ReentrantLock中,调用lock()方法获取锁;调用unlock()方法释放锁。
ReentrantLock的实现依赖于Java同步器框架AbstractQueuedSynchronizer(本文简称之为 AQS)。AQS使用一个整型的volatile变量(命名为state)来维护同步状态,马上我们会看到,这 个volatile变量是ReentrantLock内存语义实现的关键。
ReentrantLock分为公平锁和非公平锁
使用公平锁时,加锁方法lock()调用轨迹如下。
1)ReentrantLock:lock()。
2)FairSync:lock()。
3)AbstractQueuedSynchronizer:acquire(int arg)。
4)ReentrantLock:tryAcquire(int acquires)。
在第4步真正开始加锁
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState(); 获取锁的开始,首先会读volatile变量state
if (c == 0) {
如果state=0且队列不为空,有线程正在获取锁。
if (!hasQueuedPredecessors() &&
当前状态值为0,期望值也为0,则更改为更新值。
compareAndSetState(0, acquires)) {
线程获取锁成功,使用了CAS算法(单线程时偏向锁)(多线程时偏向锁变成轻量级锁)设置排他锁
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;
}
public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
protected final void setExclusiveOwnerThread(Thread thread) {
exclusiveOwnerThread = thread;
}
加锁方法首先读volatile变量state。
在使用公平锁时,解锁方法unlock()调用轨迹如下。
1)ReentrantLock:unlock();
2)AbstractQueuedSynchronizer:release(int arg);
3)Sync:tryRelease(int releases);
在第3步真正开始释放锁
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c); 释放锁的最后,写volatile变量state
return free;
}
公平锁在释放锁的最后写volatile变量,在获取锁时首先读这个变量。根据volatile的happens-before规则,释放锁的线程在写volatile变量之前可见的共享变量,在获取锁的线程读取同一个volatile变量后将立即变得对获取锁的线程可见。
非公平锁释释放和公平锁完全一样,接下来看非公平锁的获取
使用非公平锁时,加锁方法lock()调用轨迹如下。
1)ReentrantLock:lock();
2)NonfairSync:lock();
3)AbstractQueuedSynchronizer:compareAndSetState(int expect,int update);
第3部开始真正开始加锁
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update); }
该方法以原子操作的方式更新state变量,compareAndSet()方法调用简称为CAS
JDK文档对该方法的说明如下:
如果当前状态值等于预期值,则以原子方式将同步状态 设置为给定的更新值。此操作具有volatile读和写的内存语义。
总结
公平锁和非公平锁释放时,最后都要写一个volatile变量state
公平锁获取时,首先回去读这个volatile变量
非公平锁获取时,首先会用CAS更新volatile变量,这个操作同时具有volatile读和写内存语义
锁释放-获取的内存语义的实现至少有下面两种方式
(1)利用volatile变量的读-写所具有的内存语义。
(2)利用CAS所附带的volatile和volatile写的内存语义。