锁除了Synchronized之外,DougLea大神还提供了Lock来帮忙进行资源的锁定。Lock接口作为JUC的核心组件,它比synchronized更加灵活,适用更多场景。下面就简单介绍下Lock的基本实现。
基本实现锁
ReentrantLock
可重入锁
当同步代码段再次进入用相同资源锁定的同步代码段时候,重入次数+1;
ReentrantLock的基本实现:
具体实现类图如下
ReentrantLock的实现基本是依赖AQS.
AQS
大致分为两类:共享和独占。
在代码中AQS里面维护了一个双线链表,用于存储阻塞的线程。当线程竞争失败后,则会被包装秤链表中的一个节点(Node)维护起来。
Node 的源码如下:
AQS.Node
这个类是AQS双向列表里面的节点,保存了Thread以及一个waitStatus的状态位。
Thread信息不用多赘述,就是被block的Thread, 但是这个waitStatus表示什么,如下是java源码注释:
/**
* Status field, taking on only the values:
* SIGNAL: The successor of this node is (or will soon be)
* blocked (via park), so the current node must
* unpark its successor when it releases or
* cancels. To avoid races, acquire methods must
* first indicate they need a signal,
* then retry the atomic acquire, and then,
* on failure, block.
* CANCELLED: This node is cancelled due to timeout or interrupt.
* Nodes never leave this state. In particular,
* a thread with cancelled node never again blocks.
* CONDITION: This node is currently on a condition queue.
* It will not be used as a sync queue node
* until transferred, at which time the status
* will be set to 0. (Use of this value here has
* nothing to do with the other uses of the
* field, but simplifies mechanics.)
* PROPAGATE: A releaseShared should be propagated to other
* nodes. This is set (for head node only) in
* doReleaseShared to ensure propagation
* continues, even if other operations have
* since intervened.
* 0: None of the above
*
* The values are arranged numerically to simplify use.
* Non-negative values mean that a node doesn't need to
* signal. So, most code doesn't need to check for particular
* values, just for sign.
*
* The field is initialized to 0 for normal sync nodes, and
* CONDITION for condition nodes. It is modified using CAS
* (or when possible, unconditional volatile writes).
*/
volatile int waitStatus;
简要说明:
- SINGAL(信号)
该状态意味着线程因为park在block的双向队列中,只要获得锁的线程释放锁,该节点就等待着被唤醒~
- CANCELLED(取消)
在同步队列中等待的线程等待超时或被中断,需要从同步队列中取 消该 Node 的结点, 其结点的 waitStatus 为 CANCELLED,即结束状态,进入该状 态后的结点将不会再变化
- CONDITION(条件)
与同步阻塞队列一样,还有一个同步等待队列~和wait/notify接口相关,只有被notify之后的Node节点才会被放到阻塞队列当中。如果wait on a condition就会进入这个队列。
- PROPAGATE(传播)
共享模式用于notifyAll只有的一种传播模式,可以通过该节点去递归的将所有等待队列节点transfer到同步阻塞队列当中。
锁的获取
通过时序图来更加直观的了解下:
通过核心代码分解来说明流程:
- 入口ReentrantLock.lock():
sync是一个内部静态类,实现了AQS。
public void lock() {
sync.lock();
}
- sync.lock()–>非公平锁的lock:
首先通过CAS获取锁,成功则将state设置为1,失败则再去走accquire逻辑
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
- CAS来争抢独享锁–>通过unSafe来实现。
该操作依赖于UnSafe类,该类实在sun.misc包下,非java标准。但是很多基础类库依赖于该类。该类提供了很多低层次的操作,包括内存屏障,CAS, 线程同步和挂起, 分配内存等操作。
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
- AQS.acquire(1):
获取独占锁,如果失败,则通过AddWaiter将线程拆入到双向列表的尾部。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
- NofairSync.tryAccquire()
同时如果成功,则累计重入次数。
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
- AQS.addWaiter():
将当前线程封装成node,插入到双向链表的尾端。
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
- AQS.acquireQueued()
通过自旋的方式,当前节点去获取锁,获取成功则线程继续。如果获取失败,则将考虑将进程进行挂起或者check 是否被interrupt。
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
- AQS.shouldParkAfterFailedAcquire & parkAndCheckInterrupt
这两个函数就是为了判断节点是否需要被挂起~如果挂起,则用LockSupport.park挂起。
图解实例:
锁的释放
Lock.unLock()
由于所有的同步队列中的阻塞线程都被LockSupport.park了~unLock方法只是通过循环阻塞队列,将Node中需要被唤醒的Thread unPark即可。由于线程阻塞的时候是在自旋的循环中,所以被unPark的线程会立即tryAccquire操作。源码:
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
//值得注意的地方,这里是通过后往前来进行遍历的,是因为在插入的时候,可能node.next为null。具体看如下AQS.enq源码
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}
主要在enq的node.prev = t 时候,t.next 还没赋值。但是该node可以被唤醒,所以从后往前~
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
ReentrantReadWriteLock
可重入读写锁
基本原理是读写锁分离,读锁共享,写锁独占。适用于读多写少的场景。
得到WriteLock或者ReadLock有两种方法:
- 一种是通过ReentrantReadWriteLock来获取,当然这样就共享了内部的sync类进行同步
- 一种是直接new,但是需要传入一个ReentrantReadWriteLock对象,这样也依赖于ReentrantReadWriteLock对象的sync。
所以也就是读写锁的同步机制主要来源于ReentrantReadWriteLock的sync类 。
获取锁
读写锁源码(均实现Lock接口)的区别:
-
读锁
public static class ReadLock implements Lock, java.io.Serializable { private static final long serialVersionUID = -5992448646407690164L; private final Sync sync; protected ReadLock(ReentrantReadWriteLock lock) { sync = lock.sync; } public void lock() { sync.acquireShared(1); } public void unlock() { sync.releaseShared(1); } }
-
写锁
public static class WriteLock implements Lock, java.io.Serializable { private static final long serialVersionUID = -4992448646407690164L; private final Sync sync; protected WriteLock(ReentrantReadWriteLock lock) { sync = lock.sync; } public void lock() { sync.acquire(1); } public void unlock() { sync.release(1); } }
-
Sync源码:
- 获取锁
abstract static class Sync extends AbstractQueuedSynchronizer { //写锁 protected final boolean tryAcquire(int acquires) { /* * Walkthrough: * 1. If read count nonzero or write count nonzero * and owner is a different thread, fail. * 2. If count would saturate, fail. (This can only * happen if count is already nonzero.) * 3. Otherwise, this thread is eligible for lock if * it is either a reentrant acquire or * queue policy allows it. If so, update state * and set owner. */ Thread current = Thread.currentThread(); //获取重入次数 int c = getState(); //需要获取独享锁的重入数量 int w = exclusiveCount(c); if (c != 0) { // 被共享锁锁住 if (w == 0 || current != getExclusiveOwnerThread()) return false; // 通过高低16位来判断独享还是共享 if (w + exclusiveCount(acquires) > MAX_COUNT) throw new Error("Maximum lock count exceeded"); // 设置新的重入状态 setState(c + acquires); return true; } //c=0,说明当前没有线程持有锁。竞争锁,同时判断是否为公平锁 if (writerShouldBlock() || !compareAndSetState(c, c + acquires)) return false; // 竞争成功设置独享锁 setExclusiveOwnerThread(current); return true; } //读锁 protected final int tryAcquireShared(int unused) { /* * Walkthrough: * 1. If write lock held by another thread, fail. * 2. Otherwise, this thread is eligible for * lock wrt state, so ask if it should block * because of queue policy. If not, try * to grant by CASing state and updating count. * Note that step does not check for reentrant * acquires, which is postponed to full version * to avoid having to check hold count in * the more typical non-reentrant case. * 3. If step 2 fails either because thread * apparently not eligible or CAS fails or count * saturated, chain to version with full retry loop. */ Thread current = Thread.currentThread(); //获取重入状态 int c = getState(); //获取重入的独享状态 // 如果是独享锁,并且当前线程非独享线程,false if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current) return -1; // 获取重入共享状态 int r = sharedCount(c); // 公平锁和非公平锁的should block同时共享状态小于最大共享线程数量(1<<16 - 1) // 然后进行cas if (!readerShouldBlock() && r < MAX_COUNT && compareAndSetState(c, c + SHARED_UNIT)) { // 如果没有线程持有读锁,则持有并设置firstReader为当前线程和firstReaderHoldCount为1 if (r == 0) { firstReader = current; firstReaderHoldCount = 1; // 类似重入,增加firstReaderHoldCount数量 } else if (firstReader == current) { firstReaderHoldCount++; // 已经有线程持有读锁,并且非当前线程 } else { HoldCounter rh = cachedHoldCounter; if (rh == null || rh.tid != getThreadId(current)) // cacheHoldCounter用来保存最近的一次线程持有读锁和对应数量 cachedHoldCounter = rh = readHolds.get(); else if (rh.count == 0) // readHolds里面维护的是threadlocal变量来持有各个线程的tid并且记录持有的数量 readHolds.set(rh); // 对应线程持有的数量+1 rh.count++; } return 1; } // 这部分代码就不解释了,具体comment是说这部分代码冗余...看流程将基本也是在自旋然后获取共享锁以及更新cache return fullTryAcquireShared(current); } final int fullTryAcquireShared(Thread current) { /* * This code is in part redundant with that in * tryAcquireShared but is simpler overall by not * complicating tryAcquireShared with interactions between * retries and lazily reading hold counts. */ HoldCounter rh = null; for (;;) { int c = getState(); if (exclusiveCount(c) != 0) { if (getExclusiveOwnerThread() != current) return -1; // else we hold the exclusive lock; blocking here // would cause deadlock. } else if (readerShouldBlock()) { // Make sure we're not acquiring read lock reentrantly if (firstReader == current) { // assert firstReaderHoldCount > 0; } else { if (rh == null) { rh = cachedHoldCounter; if (rh == null || rh.tid != getThreadId(current)) { rh = readHolds.get(); if (rh.count == 0) readHolds.remove(); } } if (rh.count == 0) return -1; } } if (sharedCount(c) == MAX_COUNT) throw new Error("Maximum lock count exceeded"); if (compareAndSetState(c, c + SHARED_UNIT)) { if (sharedCount(c) == 0) { firstReader = current; firstReaderHoldCount = 1; } else if (firstReader == current) { firstReaderHoldCount++; } else { if (rh == null) rh = cachedHoldCounter; if (rh == null || rh.tid != getThreadId(current)) rh = readHolds.get(); else if (rh.count == 0) readHolds.set(rh); rh.count++; cachedHoldCounter = rh; // cache for release } return 1; } } } }
总结
- state状态被分为两种计算方式,高16位和低16位,低16位计算独享锁,高16位计算共享锁
- 写锁 和独享锁类似,只是过滤了一下重入的次数,流程如下:
- 判断是否已经被读锁持有或者超过了独享锁的重入最大值
- 如果没有锁,则按照公平或者非公平锁进行CAS以及设置独享锁
- 读锁 类似于共享锁,重入的次数变量没有用到,流程如下:
- 判断是否已经被写锁持有
- 判断读锁是否超过阈值同时CAS
- 然后对读锁的信息进行更新
- 读锁 内部维护了4个变量
- firstReader 第一次获取读锁的线程
- firstReaderHoldCount 第一次读锁线程持有的读锁重入数量
- cachedHoldCounter 用来记录最近线程的次数和线程信息
- readHolds ThreadLocal变量,用来记录每个线程的信息和各自持有读锁的个数
释放锁
读锁和写锁释放的源码以及注释:
//独享锁
protected final boolean tryRelease(int releases) {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
int nextc = getState() - releases;
// 判断读锁是否都已经被释放掉了
boolean free = exclusiveCount(nextc) == 0;
if (free)
// 释放读锁
setExclusiveOwnerThread(null);
//这里不用CAS是因为只有独享锁的线程才能访问这段代码
setState(nextc);
return free;
}
protected final boolean tryReleaseShared(int unused) {
Thread current = Thread.currentThread();
if (firstReader == current) {
// assert firstReaderHoldCount > 0;
if (firstReaderHoldCount == 1)
firstReader = null;
else
firstReaderHoldCount--;
} else {
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
rh = readHolds.get();
int count = rh.count;
if (count <= 1) {
readHolds.remove();
if (count <= 0)
throw unmatchedUnlockException();
}
--rh.count;
}
for (;;) {
int c = getState();
int nextc = c - SHARED_UNIT;
if (compareAndSetState(c, nextc))
// Releasing the read lock has no effect on readers,
// but it may allow waiting writers to proceed if
// both read and write locks are now free.
return nextc == 0;
}
}
总结
- 释放锁和获取锁相反,减少或者清空对应的数量或者标志位
- 写锁流程:
- 判断是否是独享线程
- 减少对应的写锁的state(低16位)
- 写锁state =0 清空独享线程
- 更新state
- 读锁流程:
- 清空firstReader,firstReaderHoldCount。更新当前线程读锁线程的持有读锁的状态(cachedHoldCounter,readHolds)
- 通过CAS设置state
总结
- 读锁和写锁均是来自于ReentranReadWriteLock
- 共享了Sync类和state
- 所以读锁和写锁之间有相互的干扰,虽然在A代码设置了读锁,B代码设置了写锁,两个是不同的lock,但是同样会相互block~
- 还有学会了三个大于号**>>>**是表示无符号数的右移
用例demo
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReentrantReadWriteLockDemo {
static ReentrantReadWriteLock reentrantReadWriteLockDemo = new ReentrantReadWriteLock();
public static void main(String[] args) throws InterruptedException {
ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLockDemo.readLock();
ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLockDemo.writeLock();
new Thread(new WriteLockClass(writeLock)).start();
Thread.sleep(1000);
new Thread(new ReadLockClass(readLock)).start();
}
static class ReadLockClass implements Runnable{
Lock lock;
ReadLockClass(Lock lock){
this.lock = lock;
}
@Override
public void run() {
try{
lock.lock();
System.out.println(this.lock.getClass().getSimpleName() + "\t is working!!!");
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
static class WriteLockClass implements Runnable{
Lock lock;
WriteLockClass(Lock lock){
this.lock = lock;
}
@Override
public void run() {
try{
lock.lock();
System.out.println(this.lock.getClass().getSimpleName() + "\t is working!!!");
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
}
StampedLock
JDK 1.8引入的新锁机制,他并没有实现任何锁的接口。可以简单认为是读写 锁的一个改进版本,读写锁虽然通过分离读和写的功能使得读和读之间可以完全 并发,但是读和写是有冲突的,如果大量的读线程存在,可能会引起写线程的饥饿。 stampedLock 是一种乐观的读策略,使得乐观锁完全不会阻塞写线程。