Java并发编程(八)ReentrantLock

一、ReentrantLock简介

ReentrantLock可重入锁,全名java.util.concurrent.locks.ReentrantLock,相当于是个最基础版本的Lock的实现,针对公平锁和非公平锁,ReentrantLock都有实现。

二、ReentrantLock特性

1、可轮询锁和定时锁

可以通过调用trylock方法,查询锁的状态,如果锁已经被其他线程持有,则不会一直阻塞下去,避免死锁。

2、公平锁和非公平锁

公平锁是按照发出请求的顺序获取锁,不允许插队;而非公平锁允许插队,二者各有优缺点,ReentradntLock都有实现。

公平锁的作用就是严格按照线程启动的顺序来执行的,不允许其他线程插队执行的;而非公平锁是允许插队的,但由于可以减少线程恢复和挂起操作,总体执行效率更高。

3、可中断锁

lockInterruptibly方法能够在获取锁的同时保持对中断的响应,因此无需创建其它类型的不可中断阻塞操作。

三、ReentrantLock原理

ReentrantLock的架构相对简单,主要包括一个Sync的内部抽象类以及Sync抽象类的两个实现类。Sync继承自AQS,Sync的两个实现类分别是NonfairSync和FairSync,即非公平锁和公平锁。下面分别针对非公平锁和公平锁讲一下ReentrantLock的原理。

1、非公平锁的lock

非公平锁lock方法源码如下:

// 非公平锁的lock方法
// java.util.concurrent.locks.ReentrantLock.NonfairSync#lock
final void lock() {
	if (compareAndSetState(0, 1))
		setExclusiveOwnerThread(Thread.currentThread());
	else
		acquire(1);
}

// java.util.concurrent.locks.AbstractQueuedSynchronizer#acquire
public final void acquire(int arg) {
	if (!tryAcquire(arg) &&
			acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
		selfInterrupt();
}

调用过程整理,总共分为两步:

第一步,直接尝试CAS抢占锁,如果抢占成功,返回成功,如果抢占失败,则进行第二步;

第二步,调用acquire方法,这个方法内做了两件事,第一件事,先调用tryAcquire方法尝试抢占锁,如果失败则第二件事,即先调用addWaiter方法将线程节点加入同步队列,然后进入acquireQueued方法尝试抢占锁。

下面分别讲这三个方法,tryAcquire、addWaiter和acquireQueued。

tryAcquire方法内,先判断当前锁是否已被抢占,如果未被抢占,那么就CAS尝试抢占锁,如果抢占成功,那么就设置当前线程为已抢占锁的线程;如果锁还未被抢占,那么判断当前线程是不是已获得锁的线程,如果是,那么就更新同步状态位,否则抢占失败。tryAcquire方法源码如下:

protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}

// java.util.concurrent.locks.ReentrantLock.Sync#nonfairTryAcquire
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;
}

addWaiter方法内,先判断当前队列是否为空,如果为空,则直接CAS设置当前节点为头结点;如果队列不为空,则CAS设置当前节点为尾节点。addWaiter方法源码如下:

// java.util.concurrent.locks.AbstractQueuedSynchronizer#addWaiter
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;
}

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;
			}
		}
	}
}

acquireQueued方法内,在一个死循环内,不断判断当前节点的前驱节点是否是头结点,如果是,则表示当前节点可抢占锁,会调用tryAcquire方法尝试抢占锁。acquireQueued方法源码如下:

// java.util.concurrent.locks.AbstractQueuedSynchronizer#acquireQueued
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);
	}
}

2、公平锁的lock

公平锁与非公平锁的实现有两个差别,一是不会直接抢占,而是只调用acquire方法;二是tryAcquire方法实现有差别,上面讲到非公平锁在抢占锁时,如果当前无线程占锁,那么就直接CAS抢占锁,而非公平锁需要先判断当前节点是否无前驱节点,如果没有前驱节点才可以抢占锁,这是为了保证FIFO。源码如下:

// 公平锁lock,其中,acquire方法与非公平锁是同一个
final void lock() {
	acquire(1);
}

// java.util.concurrent.locks.ReentrantLock.FairSync#tryAcquire
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;
}

3、unlock

unlock是不区分公平锁和非公平锁的,unlock的时候做两件事,一是同步状态更新并setExclusiveOwnerThread(null)设置当前持有锁的线程为null,二是唤醒队列中的后续节点抢占,unlock锁源码如下:

public void unlock() {
	sync.release(1);
}

// java.util.concurrent.locks.AbstractQueuedSynchronizer.release()
public final boolean release(int arg) {
	if (tryRelease(arg)) {
		Node h = head;
		if (h != null && h.waitStatus != 0)
			unparkSuccessor(h);
		return true;
	}
	return false;
}

// java.util.concurrent.locks.ReentrantLock.Sync#tryRelease
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);
	return free;
}

注:

本文中所有源码均为jdk1.7.0_79版本。

猜你喜欢

转载自blog.csdn.net/ss1300460973/article/details/84770045