1. Lock 使用
参考博客:Java中Lock锁的基本使用
2. Lock 分析
1 Lock 和 synchronized 的不同点
-
synchronized 是 Java 中的关键字, synchronized是内置的语言实现(虚拟机级);
Lock是一个接口,需要程序员实现该Lock接口(API级); -
synchronized 发生异常时,会自动释放线程占有的锁;
Lock 发生异常时,如果没有主动在finally中通过 unLock() 释放锁,可能造成死锁现象。 -
Lock 可以让等待锁的线程响应中断,线程可以中断去干别的事务,而 synchronized 却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
-
Lock 可以知道有没有成功获取锁,而synchronized却无法办到。
-
Lock 可以提高多个线程进行读操作的效率。在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。
2 主要的实现类
ReentrantLock
此类中有3个内部类,分别是Sync抽象同步器、NonfairSync非公平锁同步器、FairSync公平锁同步器。
abstract static class Sync extends AbstractQueuedSynchronizer {...}
static final class NonfairSync extends Sync{...}
static final class FairSync extends Sync {...}
Reentrant.lock()方法的调用过程
默认非公平

3 公平锁加锁过程
首先公平锁对应的是 ReentrantLock 内部静态类 FairSync
- 加锁时会先从 lock 方法中去获取锁,调用 AQS 中 的 acquire() 方法。
final void lock() {
acquire(1);
}
- acquire() 方法去调用了 tryAcquire() (FairSync 实现)方法。
public final void acquire(int arg) {
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)) {
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;
}
tryAcquire() 方法通过 getState() 获取当前同步状态,如果 state 为 0,则通过 CAS 设置该状态值,state 初始值为1,设置锁的拥有者为当前线程,tryAcquire返回true,否则返回false。如果同一个线程在获取了锁之后,再次去获取了同一个锁,状态值就加 1,释放一次锁状态值就减一,这就是可重入锁。只有线程 A 把此锁全部释放了,状态值减到0,其他线程才有机会获取锁。
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;
}
}
}
}
- 如果获取锁失败,也就是 tryAcquire 返回 false,则调用的 addWaiter(Node mode) 方法把该线程包装成一个 node 节点入同步队列(FIFO),即尝试通过 CAS 把该节点追加到队尾,如果修改失败,意味着有并发,同步器通过进入 enq 以死循环的方式来保证节点的正确添加,只有通过 CAS 将节点设置成为尾节点之后,当前线程才能从该方法中返回,否则当前线程不断的尝试设置。加入队列时,先去判断这个队列是不是已经初始化了,没有初始化,则先初始化,生成一个空的头节点,然后才是线程节点。
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);
}
}
- 加入了同步队列的线程,通过acquireQueued方法把已经追加到队列的线程节点进行阻塞,但阻塞前又通过 tryAccquire 重试是否能获得锁,如果重试成功能则无需阻塞)。
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
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;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}
- 头节点在释放同步状态的时候,会调用unlock(),而unlock会调用release(),release() 会调用 tryRelease 方法尝试释放当前线程持有的锁(同步状态),成功的话调用unparkSuccessor() 唤醒后继线程,并返回true,否则直接返回false,
注意:队列中的节点在被唤醒之前都处于阻塞状态。当一个线程节点被唤醒然后取得了锁,对应节点会从队列中删除 。

467

被折叠的 条评论
为什么被折叠?



