重入锁—-ReentrantLock

本节主要从下述四个方面介绍重入锁。

 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 }

View Code

分别以支持重入和不支持重入进行测试;

测试代码如下:

 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 }

View Code

当使用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         }

View Code

在调用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     }

View Code

从代码中可以看到,最终调用的是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     }

View Code

 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     }

View Code

公平和非公平最大区别在于:公平锁需要判断当前节点的前一个节点是否为头节点(hasQueuedPredecessors() ),如果是,才允许通过CAS算法获取锁,如果不是,表示有线程比当前线程更早的获取锁,因此需要等待前驱线程获取锁并释放锁之后才能继续获取锁。非公平不需要进行判断。公平锁保证了线程处理之间的公平,通过FIFO,保证了公平性,而代价是进行大量的线程切换。非公平锁虽然有时会造成线程饥饿,但极少的线程切换,保证了更大的吞吐量。

ReentrantLock锁默认采用的是非公平锁。

1  public ReentrantLock() {
2         sync = new NonfairSync();//默认采用非公平锁
3     }

View Code

总结:最近这几天在看《JAVA并发编程的艺术》,作者讲解的很详细,本来不想写博客,网上关于这些的文章已经非常多了,而且该书讲解的那么透彻,但后来想想,在写博客其实也是整理、梳理知识的过程,如果自己都没有弄明白,如何能够写出来。

Published by

风君子

独自遨游何稽首 揭天掀地慰生平

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注