本节主要从下述四个方面介绍重入锁。
1.什么是重入锁?
2.为什么要引用重入锁?
3.重入锁是怎么实现的?
4.分析java并发包中ReentrantLock。
什么是重入锁
重入锁,支持重进入的锁,表示该锁能够支持一个线程对它重复加锁,即线程在获得锁之后再次获取该锁时不会被阻塞。
为什么要引用重入锁?
以子类重写父类方法为例:
Mutix是不支持重入的锁。(代码摘抄自《java并发编程的艺术》)

1 import java.util.concurrent.TimeUnit;
2 import java.util.concurrent.locks.*;
3
4
5
6 public class Mutix implements Lock
7 {
8 private static class Sync extends AbstractQueuedSynchronizer{
9 //是否处于占用状态
10 protected boolean isHeldExclusively(){
11 return this.getState()==1;
12 }
13 protected boolean tryAcquire(int acquires){
14 if(compareAndSetState(0,1)){
15 setExclusiveOwnerThread(Thread.currentThread());
16 return true;
17 }
18 return false;
19
20 }
21 protected boolean tryRelease(int releases){
22 if(getState()==0)throw new IllegalMonitorStateException();
23 setExclusiveOwnerThread(null);
24 setState(0);
25
26 return true;
27
28 }
29 Condition newCondition(){
30 return new ConditionObject();
31 }
32
33 }
34 private final Sync sync=new Sync();
35
36 @Override
37 public void lock()
38 {
39 // TODO Auto-generated method stub
40 sync.acquire(1);
41 }
42
43 @Override
44 public void lockInterruptibly() throws InterruptedException
45 {
46 // TODO Auto-generated method stub
47 sync.acquireInterruptibly(1);
48 }
49
50 @Override
51 public Condition newCondition()
52 {
53 // TODO Auto-generated method stub
54 return sync.newCondition();
55 }
56
57 @Override
58 public boolean tryLock()
59 {
60 // TODO Auto-generated method stub
61 return sync.tryAcquire(1);
62 }
63
64 @Override
65 public boolean tryLock(long arg0, TimeUnit arg1)
66 throws InterruptedException
67 {
68 // TODO Auto-generated method stub
69 return sync.tryAcquireNanos(1, arg1.toNanos(arg0));
70 }
71
72 @Override
73 public void unlock()
74 {
75 // TODO Auto-generated method stub
76 sync.release(1);
77 }
78 public boolean hasQueuedThreads(){
79 return sync.hasQueuedThreads();
80 }
81
82 }
分别以支持重入和不支持重入进行测试;
测试代码如下:

1 import java.util.concurrent.locks.ReentrantLock;
2
3
4 public class TestLock
5 {
6 static Mutix lock=new Mutix();
7 // static ReentrantLock lock=new ReentrantLock();
8
9 public static class Widget {
10 public void doSomething() {
11 lock.lock();
12 try{
13 System.out.println("父类Widget,线程名称:" +Thread.currentThread().getName());
14 }finally{
15 lock.unlock();
16 }
17 }
18 }
19
20 public static class LoggingWidget extends Widget {
21 public void doSomething() {
22 lock.lock();
23 try{
24 System.out.println("子类LoggingWidget: calling doSomething,线程名称:" +Thread.currentThread().getName());
25 super.doSomething();
26 }finally{
27 lock.unlock();
28 }
29 }
30 }
31
32
33
34 /**
35 * @param args
36 */
37 public static void main(String[] args)
38 {
39 LoggingWidget test=new LoggingWidget();
40 test.doSomething();
41 }
42
43 }
当使用ReentrantLock可重入锁时,显示结果如下:
子类LoggingWidget: calling doSomething,线程名称:main
父类Widget,线程名称:main
当使用Mutix不可重入锁时,结果如下:
子类LoggingWidget: calling doSomething,线程名称:main
此时程序并没有结束,而是一直在等待父类的锁,通过jstack命令,查看到Main进程处于等待状态。按正常的处理逻辑,子类是允许调用父类的方法,故重入锁在实际应用中还是很重要的。
重入锁是怎么实现?
1)线程再次获取锁。需要判断当前获取锁的线程是否为当前占据锁的线程,如果是,则再次获取成功。
2)锁的最终释放。若线程重复了N次获得了锁,那需要释放N次才能真正释放该锁。线程再次获取锁时,计时器加1,当释放锁时,计数器减1。
分析java并发包中ReentrantLock。
1)自定义组合同步器实现锁的获取和释放,默认是以非公平形式获取锁。

1 final boolean nonfairTryAcquire(int acquires) {
2 final Thread current = Thread.currentThread();//当前线程
3 int c = getState();//获取同步状态
4 if (c == 0) {//如果同步状态为0,表示当前没有线程获取锁
5 if (compareAndSetState(0, acquires)) {//通过CAS算法获取锁
6 setExclusiveOwnerThread(current);
7 return true;
8 }
9 }
10 else if (current == getExclusiveOwnerThread()) {
11 //如果同步状态不为0,表示已经有线程获取了该锁,判断获取锁的线程是否为当前线程,如果时,同步状态加acquires,一般为加1
12 int nextc = c + acquires;
13 if (nextc < 0) // overflow
14 throw new Error("Maximum lock count exceeded");
15 setState(nextc);//设置同步状态
16 return true;
17 }
18 return false;
19 }
20
21 protected final boolean tryRelease(int releases) {
22 int c = getState() - releases;//计算当前同步状态减去releases
23 if (Thread.currentThread() != getExclusiveOwnerThread())//如果当前线程不等于获取锁的线程
24 throw new IllegalMonitorStateException();
25 boolean free = false;
26 if (c == 0) {//如果C==0,表示同步状态完全释放,将占用线程设置为Null,并且返回true
27 free = true;
28 setExclusiveOwnerThread(null);
29 }
30 setState(c);
31 return free;
32 }
在调用ReentrantLock的lock和unlock方法时,实际是调用同步器中的方法。

1 public boolean tryLock() {
2 return sync.nonfairTryAcquire(1);
3 }
4
5 public boolean tryLock(long timeout, TimeUnit unit)
6 throws InterruptedException {
7 return sync.tryAcquireNanos(1, unit.toNanos(timeout));
8 }
9 public void unlock() {
10 sync.release(1);
11 }
从代码中可以看到,最终调用的是sync中的方法,对于用户而言,sync是透明的。
2)公平和非公平获取锁。

1 /**
2 * Sync object for non-fair locks
3 */
4 static final class NonfairSync extends Sync {
5 private static final long serialVersionUID = 7316153563782823691L;
6
7 /**
8 * Performs lock. Try immediate barge, backing up to normal
9 * acquire on failure.
10 */
11 final void lock() {
12 if (compareAndSetState(0, 1))
13 setExclusiveOwnerThread(Thread.currentThread());
14 else
15 acquire(1);
16 }
17
18 protected final boolean tryAcquire(int acquires) {
19 return nonfairTryAcquire(acquires);
20 //直接调用Sync的方法。
21 }
22 }

1 static final class FairSync extends Sync {
2 private static final long serialVersionUID = -3000897897090466540L;
3
4 final void lock() {
5 acquire(1);
6 }
7
8 /**
9 * Fair version of tryAcquire. Don't grant access unless
10 * recursive call or no waiters or is first.
11 */
12 protected final boolean tryAcquire(int acquires) {
13 final Thread current = Thread.currentThread();
14 int c = getState();
15 if (c == 0) {
16 if (!hasQueuedPredecessors() &&
17 compareAndSetState(0, acquires)) {
18 setExclusiveOwnerThread(current);
19 return true;
20 }
21 }
22 else if (current == getExclusiveOwnerThread()) {
23 int nextc = c + acquires;
24 if (nextc < 0)
25 throw new Error("Maximum lock count exceeded");
26 setState(nextc);
27 return true;
28 }
29 return false;
30 }
31 }
公平和非公平最大区别在于:公平锁需要判断当前节点的前一个节点是否为头节点(hasQueuedPredecessors() ),如果是,才允许通过CAS算法获取锁,如果不是,表示有线程比当前线程更早的获取锁,因此需要等待前驱线程获取锁并释放锁之后才能继续获取锁。非公平不需要进行判断。公平锁保证了线程处理之间的公平,通过FIFO,保证了公平性,而代价是进行大量的线程切换。非公平锁虽然有时会造成线程饥饿,但极少的线程切换,保证了更大的吞吐量。
ReentrantLock锁默认采用的是非公平锁。

1 public ReentrantLock() {
2 sync = new NonfairSync();//默认采用非公平锁
3 }
总结:最近这几天在看《JAVA并发编程的艺术》,作者讲解的很详细,本来不想写博客,网上关于这些的文章已经非常多了,而且该书讲解的那么透彻,但后来想想,在写博客其实也是整理、梳理知识的过程,如果自己都没有弄明白,如何能够写出来。
来源:https://www.cnblogs.com/icbcfang/p/4769872.html
