并发编程之Lock锁原理
J.U.C
Lock存在于J.U.C(Java.util.Concurrent)下。里面提供了很多工具,例如:BlockingQueue、ConcurrentHashMap、线程池、CountDownLatch等。
Lock与Synchronized
锁是用来解决线程安全问题的,它与Synchronized的作用是完全一样的,只是实现方式不同。
ReentrantLock
ReentrantLock又叫重入锁,是一种互斥锁,即一个线程获取了同一把锁,后续在释放这把锁之前,获取这把锁不需要加锁,只要记录重入的次数,它的重入特性与Synchronized一致的。
ReentrantLock使用
看下面的代码,代码的结果预期值为1000,但是实际值往往会比1000要小,这是由于inc()方法的是一个非原子性操作导致的。
public class LockTest {
private static int count = 0;
public static void inc() {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
count++;
}
public static void main(String[] args) throws InterruptedException {
for(int i=0; i<1000; i++) {
new Thread(()->LockTest.inc()).start();
}
Thread.sleep(3000);
System.out.println("Result:"+count);
}
}
Result:983
怎么解决这个原子性问题呢?
public class LockTest {
static Lock lock = new ReentrantLock();
private static int count = 0;
public static void inc() {
//抢占锁
lock.lock();
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
count++;
}
public static void main(String[] args) throws InterruptedException {
for(int i=0; i<1000; i++) {
new Thread(()->LockTest.inc()).start();
}
Thread.sleep(3000);
System.out.println("Result:"+count);
}
}
Result:1000
ReentrantLock实现原理
锁的核心功能:
满足线程的互斥特性
同一个时刻,只允许一个线程进入到加锁的代码中。保证多线程环境下,线程的顺序访问。
锁的设计猜想
- 一定会涉及到锁的抢占,需要一个标记来实现互斥。需要一个全局变量(0,1)
- 抢占到了锁,不需要处理
- 没抢占到锁,怎么处理
- 需要等待(让处于排队中的线程,如果没有抢占到锁,则直接阻塞,释放CPU资源)
- 如何让线程等待
- wait/notify(线程通信机制,无法指定唤醒某个线程)
- LockSupport.park/unpark(阻塞一个指定线程,唤醒一个指定线程)
- Condition
- 如何让线程等待
- 需要排队(允许有N个线程被阻塞,此时线程处于活跃状态)
- 通过一个数据结构,把这N个排队的线程存储起来
- 需要等待(让处于排队中的线程,如果没有抢占到锁,则直接阻塞,释放CPU资源)
- 抢占到锁的释放过程,如何处理
- LockSupport.unpark,唤醒一个处于队列中的指定线程
- 锁抢占的公平性(是否允许插队)
- 公平
- 非公平
AbstractQueuedSynchronizer(AQS)
上述对于锁的猜想,实际上是在AQS中实现的,Lock本身没有做特殊处理,核心的机制都在AQS中实现。AQS里面提供了两种机制:
- 共享锁
- 互斥锁
ReentrantLock实现原理分析
Lock
ReetrantLock
公平锁和非公平锁
公平锁
final void lock() {
acquire(1); //抢占1把锁.
}
public final void acquire(int arg) { // AQS里面的方法
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) { //表示无锁状态
if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {
//CAS(#Lock) -> 原子操作| 实现互斥的判断
setExclusiveOwnerThread(current); //把获得锁的线程保存到 exclusiveOwnerThread中
return true;
}
}
//如果当前获得锁的线程和当前抢占锁的线程是同一个,表示重入
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires; //增加重入次数.
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc); //保存state
return true;
}
return false;
}
非公平锁
final void lock() {
//不管当前AQS队列中是否有排队的情况,先去插队
if (compareAndSetState(0, 1)) //返回false表示抢占锁失败
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
public final void acquire(int arg) { // AQS
if(!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//hasQueuedPredecessors
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;
}
加入队列并进行自旋等待
public final void acquire(int arg) {
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
- addWaiter(Node.EXCLUSIVE) -> 添加一个互斥锁的节点
- acquireQueued() -> 自旋锁和阻塞的操作
private Node addWaiter(Node mode) {
//把当前线程封装成一个Node节点。
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;
}
private Node enq(final Node node) {
for (;;) {//自旋
Node t = tail;
if (t == null) { // Must initialize
//初始化一个head节点
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
//node表示当前来抢占锁的线程,有可能是ThreadB、 ThreadC。。
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) { //自旋
//begin ->尝试去获得锁(如果是非公平锁)
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) { //如果返回true,则不需要等待,直接返回
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//end
//否则,让线程去阻塞(park)
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt()) //LockSupport.park
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
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);
}
}
//ThreadB、 ThreadC、ThreadD、ThreadE -> 都会阻塞在下面这个代码的位置.
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this); //被唤醒. (interrupt()->)
return Thread.interrupted(); //中断状态(是否因为中断被唤醒的.)
}
unlock
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head; //得到当前AQS队列中的head节点。
if (h != null && h.waitStatus != 0) //head节点不为空
unparkSuccessor(h); //
return true;
}
return false;
}
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0) //表示可以唤醒状态
compareAndSetWaitStatus(node, ws, 0); //恢复成0
Node s = node.next;
if (s == null || s.waitStatus > 0) { //说明ThreadB这个线程可能已经被销毁,或 者出现异常...
s = null;
//从tail -> head进行遍历.
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0) //查找到小于等于0的节点
s = t;
}
if (s != null)
LockSupport.unpark(s.thread); //封装在Node中的被阻塞的线程。ThreadB、ThreadC。
}