0%

Lock

Lock

由于synchronized在很多情况下是不可控的,所以在jdk5出现了一个新的加锁方式Lock,提供了无条件的,可轮询的,可定时的,可中断的所获取操作,所有加锁和解锁都是显式的 悲观锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public interface Lock {
// 获取锁,会阻塞
void lock();
// 如果当前线程未被中断,则获取锁
void lockInterruptibly() throws InterruptedException;
// 尝试获取锁,如果锁可用,则获取锁,返回true;如果锁不可用,则返回false
boolean tryLock();
// 尝试获取锁,设置超时时间,如果该时间内没有获取到锁,则返回false
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
// 释放锁
void unlock();
// 将Condition绑定到该Lock实例上
Condition newCondition();
}

主要有三个实现类ReentrantLock重入锁、ReentrantReadWriteLock.ReadLock读锁、ReentrantReadWriteLock.WriteLock写锁

ReentrantLock

ReentrantLock为Lock的实现类,实现了标准的互斥锁,使用CAS,比synchronized效率略高,一次最多只有一个线程持有ReentrantLock —> 有公平锁和非公平锁,是可重入锁,每次重入会把拥有数++

1
2
3
4
5
6
7
8
9
10
11
private transient volatile Node head;

private transient volatile Node tail;

// Node存储等待的线程 本质是一个双向链表
static final class Node{
// 节点等待状态
volatile int waitStatus;
volatile Node prev;
volatile Node next;
}

公平锁和非公平锁

ReentrantLock默认是非公平锁

1
2
3
public ReentrantLock() {
sync = new NonfairSync();
}
公平锁

java.util.concurrent.locks.ReentrantLock.FairSync 公平锁

公平锁是指多个线程按照申请锁的顺序来获取锁,线程直接进入队列中排队,队列中的第一个线程才能获得锁。

  • 优点是等待锁的线程不会饿死。

  • 缺点是整体吞吐效率相对非公平锁要低,等待队列中除第一个线程以外的所有线程都会阻塞,CPU唤醒阻塞线程的开销比非公平锁大

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;

final void lock() {
acquire(1);
}

/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
// 会尝试再次通过CAS获取一次锁
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// getState 为0,表示没有线程持有锁
if (c == 0) {
// 看当前线程是否在队列头,更改status是否成功
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
// 设置当前线程为占有线程
setExclusiveOwnerThread(current);
return true;
}
}
// getState 不为0,表示有线程持有锁,判断是否为当前线程占有
else if (current == getExclusiveOwnerThread()) {
// 将进入次数加一,由于是可重入锁,所以进入一次就加一,出去一次就减一
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}

// AbstractQueuedSynchronizer中的acquire方法
public final void acquire(int arg) {
// tryAcquire会尝试再次通过CAS获取一次锁
// tryAcquire为true则成功获取到锁
// tryAcquire为false,则需要入队
if (!tryAcquire(arg) &&
// Node.EXCLUSIVE表示独占锁模式 ReentrantLock是独占锁
// acquireQueued 通过自旋,判断当前队列节点是否可以获取锁
// addWaiter 将当前线程加入上面锁的双向链表(等待队列)中
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}


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;
}
非公平锁

java.util.concurrent.locks.ReentrantLock.NonfairSync 非公平锁

非公平锁是多个线程加锁时直接尝试获取锁,获取不到才会到等待队列的队尾等待。但如果此时锁刚好可用,那么这个线程可以无需阻塞直接获取到锁,所以非公平锁有可能出现后申请锁的线程先获取锁的场景。

  • 优点是可以减少唤起线程的开销,整体的吞吐效率高,因为线程有几率不阻塞直接获得锁,CPU不必唤醒所有线程

  • 缺点是处于等待队列中的线程可能会饿死,或者等很久才会获得锁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;

/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
// 非公平锁先尝试获取锁,获取到锁就直接占有
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}

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

// Sync的nonfairTryAcquire方法
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// getState 为0,表示没有线程持有锁
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.未占用 1.已占用 大于1 重入 使用CAS来修改state状态

使用多条件

使用Lock可以一个锁关联一个或多个条件,这些条件通过Condition接口声明,该接口中提供了挂起线程和唤醒线程的机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public interface Condition {


void await() throws InterruptedException;


void awaitUninterruptibly();


long awaitNanos(long nanosTimeout) throws InterruptedException;


boolean await(long time, TimeUnit unit) throws InterruptedException;


boolean awaitUntil(Date deadline) throws InterruptedException;


void signal();


void signalAll();
}

ReadWriteLock

ReadWriteLock读写锁暴露了两个Lock,一个用来读,一个用来写。加锁策略是允许多个读者同时读,但是只能有一个写者,多个读锁不互斥,读锁与写锁互斥,写锁与写锁互斥

1
2
3
4
5
6
public interface ReadWriteLock {
// 读锁是共享锁
Lock readLock();
// 写锁是独占锁
Lock writeLock();
}

ReentrantReadWriteLock为ReadWriteLock的实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
// 获取写锁
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();
// 在state中会使用高16位表示读锁状态(读锁个数),低16位表示写锁状态(写锁个数)
// 当前锁的个数
int c = getState();
// 写锁的个数
int w = exclusiveCount(c);
// 当前有线程持有锁
if (c != 0) {
// (Note: if c != 0 and w == 0 then shared count != 0)
// 没有写锁说明全是读锁, 或者当前线程不是持有锁的线程
if (w == 0 || current != getExclusiveOwnerThread())
return false;
// 写锁的数量大于最大值
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// Reentrant acquire 走到这里说明是当前线程重入
setState(c + acquires);
return true;
}
// 此时持有锁的数量时0
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
setExclusiveOwnerThread(current);
return true;
}

static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }



// 获取读锁
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();
// 已经有其他线程获取到写锁了,此时无法获取读锁
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
int r = sharedCount(c);
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
if (r == 0) {
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
}
return 1;
}
return fullTryAcquireShared(current);
}

与synchronized比较

  • Lock使用代码实现CAS,为API层面上的互斥锁;synchronized通过jvm的monitor实现的,为原语层面的互斥锁
  • 使用ReentrantLock可以设置尝试获取锁的等待时间,超过该时间则中断等待,可以实现公平锁,synchronized是非公平锁
  • ReentrantLock可以绑定多个条件Condition对象