ReentranrLock

ReentrantLock是可重入锁,它实现了Lock接口。

可重入锁就是说同一个线程可以多次申请到该锁。

ReentrantLock有公平锁和非公平锁两种方式。

1
2
3
4
5
6
7
8
public void m() {
* lock.lock(); // block until condition holds
* try {
* // ... method body
* } finally {
* lock.unlock()
* }
* }

使用lock和unlock进行加锁和解锁。

而lock和unlock都是调用sync的。

1
2
3
4
5
6
7
8
9
//加锁
public void lock() {
sync.lock();
}

//释放锁
public void unlock() {
sync.release(1);
}

sync是ReentranrtLock的成员变量,他继承了AQS,所以,核心是AQS的实现,而且有两个内部类,一个可以实现公平锁,一个实现非公平锁

1
2
3
4
private final Sync sync;
abstract static class Sync extends AbstractQueuedSynchronizer {}
static final class NonfairSync extends Sync{}
static final class FairSync extends Sync{}

在构造函数中可以定义是公平锁还是非公平锁。

1
2
3
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}

ReentrantLock主要还是基于AQS实现的,我们主要关注他重写的一些方法,包括tryAcquire()和release().

tryAcquire()

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

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

公平锁和非公平锁最大的区别就是!hasQueuedPredecessors(),公平锁需要先判断等待队列中是否有前驱节点在等待。如果有,则说明有线程比当前线程更早的请求资源,根据公平性,当前线程请求资源失败;如果当前节点没有前驱节点,才有做后面的逻辑判断的必要性。

公平锁

非公平锁的吞吐量较高例如默认状态的ReentrantLock 有新线程来了先争夺一下锁,没成功再去排队。
公平锁是java关键字synchronized的重锁模式,谁来了都乖乖排队,后来的线程不能争夺锁,一定要入队列等待前一个线程来unpark自己,除非队列里没有其他线程。

可以在构造函数中设置公平锁还是非公平锁。

尝试锁定

可以使用tryLock()来尝试上锁,假如在一定的时间内获取锁失败,那么就会放弃等待

可中断锁

lock.lockInterruptibly() 对线程中断 interrupt() 做出响应。

使用 lockInterruptibly() 则该线程在等待锁的过程中,如果被中断interrupt(),则直接抛出中断异常来立即响应中断。

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
package JUC;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLock3 {
Lock lock=new ReentrantLock();
void m1() {
lock.lock();
System.out.println("t1 start");
try {
TimeUnit.SECONDS.sleep(10000000);
System.out.println("t1 end");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
lock.unlock();
}
}
void m2() {
try {
lock.lockInterruptibly();
System.out.println("t2 start");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
System.out.println("t2 interrupted!");
}
}
public static void main(String[] args) {
ReentrantLock3 test=new ReentrantLock3();
Thread t2=new Thread(test::m2);
new Thread(test::m1).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
t2.start();
t2.interrupt();
}

}
//t1 start
//t2 interrupted!

与synchronized区别

  • ReentrantLock是JDK实现的,synchronized是JVM实现的。
  • ReentrantLock需要手动释放,而且最好在finally中释放。
  • ReentrantLock支持公平锁。
  • ReentrantLock支持中断锁。
  • ReentrantLock支持多个条件队列。
  • 一个底层实现是CAS,一个实现是操作系统的monitor。

现在来说,经过JVM的优化,synchronized的效率已经很高了,一般来说,如果没有必须要使用ReentrantLock的功能。最好使用synchronized。因为JVM 原生地支持它,而 ReentrantLock 不是所有的 JDK 版本都支持。