ReentrantLock用于java的多线程编程中对临界资源的互斥访问,作为synchronized的补充。
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
try {
lock.lock();
System.out.println(100);
} finally {
lock.unlock();
}
});
Thread t2 = new Thread(() -> {
//if (lock.tryLock())
try {
lock.lock();
System.out.println(200);
} finally {
lock.unlock();
}
});
Thread t3 = new Thread(() -> {
try {
lock.lock();
System.out.println(300);
} finally {
lock.unlock();
}
});
t1.start();
t2.start();
t3.start();
借助于上述代码分析ReentrantLock的实现:
假设线程t1获取锁,此时调用
public void lock() {
sync.lock();
}
sync为ReentrantLock的实例成员,根据构造函数的参数设置为公平锁和非公平锁(默认)。此时sync为非公平锁。调用NonfairSync类的
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
这里先判断state变量是不是0,是0则设置为1,并且把当前持有排他锁的线程设置为当前线程(这里为t1),此时表明t1获取到锁。这个时候如果线程t2再获取锁的话则进入else分支。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
在else分支里,首先调用tryAcquire,最终调到
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;
}
先判断state是否为0,此时因为t1获取锁肯定不为0,再看当前获取锁的线程是不是和持有排他锁的线程是同一个线程,这里表明ReentrantLock是递归锁,同一个线程中多次执行获取锁的代码时线程不会被阻塞。如果都不是则返回false表明获取锁失败。这个时候就要把当前线程加入到等待队列中。
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;
}
这里mode为null,在非公平锁中没有用到。先看队列的尾部是不是null,如果tail不为null,表明已经有线程加入到了等待队列,这个时候只要把node节点加入到队列中就可以。调用compareAndSetTail是防止别的线程此时也要加入到等待队列,这里对等待队列的修改需要互斥。如果tail不为null则调用
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;
}
}
}
}
第一遍循环里面,t为null,将队列的head设置为头结点(空的Node),并把tail指向该节点。第二遍循环就是双链表的常规操作,加入node节点,并设置tail,此时返回的是那个空节点,而不是当前的node。而在addWaiter里返回的是新加入的节点。
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);
}
}
这里先判断node节点的前驱是不是head,如果是head并且获取锁成功则要重新设置head。否则把当前节点的前驱的waitStatue设置为SIGNAL(-1),表明他的下一个节点需要在当前锁被释放时被唤醒。最后调用parkAndCheckInterrupt()把当前线程挂起。t3获取锁的时候同样失败被加入到等待队列。
当调用lock.unlock()唤醒其他线程时可以看到,从等待队列里面摘下一个节点并唤醒其投入运行要经过不少的步骤,此时如果有其他线程试图获取锁(调用lock函数)则大概率通过了if条件中的compareAndSetState测试,此时该线程获取到了锁,被摘下的线程获取锁失败继续被挂起,这就是非公平锁,但正是由于这样增加了效率,导致非公平锁的效率相对于公平锁更高(公平锁则要按照等待队列中的顺序依次唤醒线程获取锁)。