【JAVA多线程07】ReentrantLock重入锁

采用synchronized进行加锁,是由jvm内部实现的 称为:内置锁。从java1.5开始,jdk api引入了新锁API 他们都继承自Lock,称为:显式锁,比如今天的主题ReentrantLock。

之所以称做显式锁,主要有两点原因:

1、相对于内置锁是有jvm内部实现的,显式锁是在使用java api实现的,确切的说是基于AQS实现的(对AQS的理解后面会写文章分析);

2、使用Lock加锁,需要显式的加锁以及释放锁,相对于内置锁使用synchronized而言,要麻烦些。下面来看看ReentrantLock的基本用法:

 

Java代码  收藏代码

  1. public class ReentrantLockTest {  
  2.    
  3.     public static void main(String[] args) {  
  4.         ReentrantLock lock = new ReentrantLock();  
  5.         lock.lock();  
  6.         try{  
  7.             //业务方法  
  8.         }catch (Exception e){  
  9.             //业务异常  
  10.         }finally {  
  11.             lock.unlock();  
  12.         }  
  13.     }  
  14. }  

 

从表面上看,使用显式锁比使用内置锁更繁琐,需要手动调用lock加锁和unlock解锁,如果忘记unlock 就会导致其他线程永远无法获取到锁的严重错误。既然为何还要新增显式锁呢?

 

简单的讲,显式锁的内置锁的补充:显式锁Lock提供了中断锁、定时锁、可轮询等实现,这些都是内置锁synchronized不具备的;显式锁可以指定为公平锁或非公平锁,而内置锁synchronized锁是非公平的。使用synchronized加锁,有些情况下很容易导致死锁,在这种情况下改用显式锁定时功能 在一段时间没有获取到锁,就放弃获取锁 就可以避免死锁。

 

另外与内置锁还有一个明显不同的地方是,内置锁可以用在方法上,而显式锁只能用在代码块上,也就是说强制使用更细粒度的加锁。

 

可以说内置锁和显式锁是互补关系:显式锁不能用在方法上,而且容易忘记释放锁;内置锁不可中断,有些情况下容易产生死锁,另外内置锁无法实现公平锁。根据这些差异在自己的实际业务场景中选择性使用即可,需要说明下的是显式锁的性能比内置锁性能稍微好些。

 

理论总结到处结束,下面开始以ReentrantLock锁分析下显式锁的实现原理。主要包括:排它锁、公平锁、非公平锁、中断锁、延迟锁、轮询锁、重入锁的实现原理。

 

ReentrantLock实现原理

 

对AQS的实现

首先需要说明的ReentrantLock与内置锁一样都是排它锁,从名字上开是与内置锁一样是可重入的。ReentrantLock与前两篇文章中讲的Semaphore和CountDownLatch一样都是基于AQS实现的,只是Semaphore和CountDownLatch都是共享锁的实现方法,而ReentrantLock是排它锁实现,也就是说ReentrantLock的内部类在实现AQS时是对tryAcquire、tryRelease这两个方法进行扩展的。首先来看内部类Sync对AQS的实现(Sync还有两个子类,分别对应公平和非公平锁实现):

Java代码  收藏代码

  1. abstract static class Sync extends AbstractQueuedSynchronizer {  
  2.     private static final long serialVersionUID = -5179523762034025860L;  
  3.    
  4.     //交给公平和非公平的子类去实现  
  5.     abstract void lock();  
  6.    
  7.     //非公平的排它尝试获取锁实现  
  8.     final boolean nonfairTryAcquire(int acquires) {  
  9.         final Thread current = Thread.currentThread();  
  10.         int c = getState();  
  11.         //如果AQS的state为0说明获得锁,并且对state加1,其他线程获取锁时被阻塞  
  12.         if (c == 0) {  
  13.             if (compareAndSetState(0, acquires)) {  
  14.                 setExclusiveOwnerThread(current);  
  15.                 return true;  
  16.             }  
  17.         }  
  18.          
  19.         //判断线程是不是重新获取锁,如果是 无需排队,对AQS的state+1处理,这就是重入锁的实现  
  20.         else if (current == getExclusiveOwnerThread()) {  
  21.             int nextc = c + acquires;  
  22.             if (nextc < 0// overflow  
  23.                 throw new Error("Maximum lock count exceeded");  
  24.             setState(nextc);  
  25.             return true;  
  26.         }  
  27.         return false;//都不满足获取锁失败,进入AQS队列阻塞  
  28.     }  
  29.    
  30.     //公平的排它尝试释放锁实现  
  31.     protected final boolean tryRelease(int releases) {  
  32.         int c = getState() - releases;//对应重入锁而言,在释放锁时对AQS的state字段减1  
  33.         if (Thread.currentThread() != getExclusiveOwnerThread())  
  34.             throw new IllegalMonitorStateException();  
  35.         boolean free = false;  
  36.         if (c == 0) {//如果AQS的状态字段已变为0,说明该锁被释放  
  37.             free = true;  
  38.             setExclusiveOwnerThread(null);  
  39.         }  
  40.         setState(c);  
  41.         return free;  
  42.     }  
  43.    
  44.     //判断是否是当前线程持有锁  
  45.     protected final boolean isHeldExclusively() {  
  46.         // While we must in general read state before owner,  
  47.         // we don't need to do so to check if current thread is owner  
  48.         return getExclusiveOwnerThread() == Thread.currentThread();  
  49.     }  
  50.    
  51.     //获取条件队列  
  52.     final ConditionObject newCondition() {  
  53.         return new ConditionObject();  
  54.     }  
  55.    
  56.     // Methods relayed from outer class  
  57.    
  58.     final Thread getOwner() {  
  59.         return getState() == 0 ? null : getExclusiveOwnerThread();  
  60.     }  
  61.    
  62.     final int getHoldCount() {  
  63.         return isHeldExclusively() ? getState() : 0;  
  64.     }  
  65.    
  66.     final boolean isLocked() {  
  67.         return getState() != 0;  
  68.     }  
  69.    
  70.     /** 
  71.      * 说明ReentrantLock是可序列化的 
  72.      */  
  73.     private void readObject(java.io.ObjectInputStream s)  
  74.             throws java.io.IOException, ClassNotFoundException {  
  75.         s.defaultReadObject();  
  76.         setState(0); // reset to unlocked state  
  77.     }  
  78. }  

 

 

非公平锁

非公平锁实现:

Java代码  收藏代码

  1. static final class NonfairSync extends Sync {  
  2.     private static final long serialVersionUID = 7316153563782823691L;  
  3.    
  4.     /** 
  5.      * Performs lock.  Try immediate barge, backing up to normal 
  6.      * acquire on failure. 
  7.      */  
  8.     final void lock() {  
  9.         //判断当前state是否为0,如果为0直接通过cas修改状态,并获取锁  
  10.         if (compareAndSetState(01))  
  11.             setExclusiveOwnerThread(Thread.currentThread());  
  12.         else  
  13.             acquire(1);//否则进行排队  
  14.     }  
  15.    
  16.     //调用父类的的非公平尝试获取锁  
  17.     protected final boolean tryAcquire(int acquires) {  
  18.         return nonfairTryAcquire(acquires);  
  19.     }  
  20. }  

 

非公平锁的实现很简单,在lock获取锁时首先判断判断当前锁是否可以用(AQS的state状态值是否为0),如果是 直接“插队”获取锁,否则进入排队队列,并阻塞当前线程。

 

公平锁

公平锁实现:

Java代码  收藏代码

  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.      * 公平锁实现 尝试获取实现方法, 
  10.      * Fair version of tryAcquire.  Don't grant access unless 
  11.      * recursive call or no waiters or is first. 
  12.      */  
  13.     protected final boolean tryAcquire(int acquires) {  
  14.         final Thread current = Thread.currentThread();  
  15.         int c = getState();  
  16.         if (c == 0) {  
  17.             //AQS队列为空,或者当前线程是头节点 即可获的锁  
  18.             if (!hasQueuedPredecessors() &&  
  19.                     compareAndSetState(0, acquires)) {  
  20.                 setExclusiveOwnerThread(current);  
  21.                 return true;  
  22.             }  
  23.         }  
  24.         //重入锁实现  
  25.         else if (current == getExclusiveOwnerThread()) {  
  26.             int nextc = c + acquires;  
  27.             if (nextc < 0)  
  28.                 throw new Error("Maximum lock count exceeded");  
  29.             setState(nextc);  
  30.             return true;  
  31.         }  
  32.         return false;  
  33.     }  
  34. }  

公平锁的实现,跟Semaphore一样在tryAcquire方法实现中通过hasQueuedPredecessors方法判断当前线程是否是AQS队列中的头结点或者AQS队列为空,并且当前锁状态可用 可以直接获取锁,否则需要排队。

 

重入锁

不论是公平锁还是非公平锁的实现中,在tryAcquire方法中判断如果锁已经被占用,都会判断是否是当前线程占用,如果是 可以再次获取锁(无需排队),并对AQS的state字段加1;在释放锁时每次都减1,直到为0时,其他线程在可用。顺便提一下内置锁synchronized也是可以重入的。

Java代码  收藏代码

  1. //重入锁实现  
  2.         else if (current == getExclusiveOwnerThread()) {  
  3.             int nextc = c + acquires;  
  4.             if (nextc < 0)  
  5.                 throw new Error("Maximum lock count exceeded");  
  6.             setState(nextc);  
  7.             return true;  
  8.         }  
  9.    

 

排它锁

从两个tryAcquire的实现可以看出ReentrantLock的排它锁实现,根本上是通过AQS的state字段保证的,每次获取锁时都是首先判断state是否为0,并且只有1个线程能获取到锁。这个特性与内置锁synchronized相同。

 

关于ReentrantLock对AQS的三个内部类实现分析完毕,接下来看下ReentrantLock的核心方法,实现都很简单基本都是直接调用FairSync或者NonfairSync对AQS的实现。比如lock和unlock方法:

Java代码  收藏代码

  1. public void lock() {  
  2.         sync.lock();  
  3.     }  
  4.    
  5.     public void unlock() {  
  6.         sync.release(1);  
  7.     }  
  8.    

 

延迟锁

延迟锁,指的是在指定时间内没有获取到锁,就取消阻塞并返回获取锁失败,由调用线程自己决定后续操作,比如放弃操作或者创建轮询获取锁。

Java代码  收藏代码

  1. public boolean tryLock(long timeout, TimeUnit unit)  
  2.             throws InterruptedException {  
  3.         //调用AQS带延时功能获取方法  
  4.         return sync.tryAcquireNanos(1, unit.toNanos(timeout));  
  5. }  

 

中断锁

tryLock(long timeout, TimeUnit unit)这个方法会抛出InterruptedException异常,可以用于实现中断锁,即等待的时间还未到,可以直接调用interrupt方法以中断获取锁。另外ReentrantLock还有一个方法可以实现中断锁,即:lockInterruptibly方法

Java代码  收藏代码

  1. public void lockInterruptibly() throws InterruptedException {  
  2.         //直接调用AQS的可中断获取方法  
  3.         sync.acquireInterruptibly(1);  
  4.     }  

 

通过调用该方法获取锁跟lock方法一样,如果获取不到会阻塞,不同的是使用这个方法获取锁是可以在外部中断的,但lock方法不行。使用灵活使用可中断锁,可以防止死锁。

 

Ps:中断锁是对获取锁的中断,注意与线程被中断的区别(虽然本质上都是中断线程)。不管是使用显式锁还是内置锁的线程阻塞都是可以被中断的,而中断锁是指线程在获取锁的过程中被阻塞 可以中断现在继续排队获取锁的过程。中断锁:是中断获取锁排队阻塞,可以使用ReentrantLock的tryLock(long timeout, TimeUnit unit)、lockInterruptibly()这两个方法实现,而使用内置锁synchronized 如果线程已经在排队获取锁是无法被中断的;中断线程:是中断线程执行业务方法过程中的阻塞(如sleep阻塞,BlookingQueue阻塞)。

 

轮询锁

tryLock(long timeout, TimeUnit unit)这个方法延迟获取锁的方法,另外ReentrantLock还有一个非延迟也不阻塞的获取锁方法tryLock(),尝试获取锁,如果没有获取到直接反回false 不阻塞线程:

Java代码  收藏代码

  1. public boolean tryLock() {  
  2.         //默认只有非公平实现  
  3.         return sync.nonfairTryAcquire(1);  
  4. }  

ReentrantLock不直接提供轮询锁api,但可以用tryLock(long timeout, TimeUnit unit)和tryLock() 这两个方法实现。即没有获取到锁,可以使用while循环 隔一段时间再次获取,直到获取到为止,这种方式是解决死锁的常用手段。这两个方法的使用区别:tryLock(long timeout, TimeUnit unit)不用在while中sleep,而tryLock()需要自己在while中sleep一会儿,减少资源开销。以tryLock()为例 实现轮询锁:

Java代码  收藏代码

  1. public class ReentrantLockTest {  
  2.    
  3.     public static void main(String[] args) throws Exception{  
  4.         ReentrantLock lock = new ReentrantLock(true);  
  5.         while (true){  
  6.             if(lock.tryLock()){//这里不阻塞  
  7.                 try{  
  8.                     System.out.println("执行业务方法");  
  9.                     //业务方法  
  10.                     return;  
  11.                 }catch (Exception e){  
  12.                     //业务异常  
  13.                 }finally {  
  14.                     lock.unlock();  
  15.                 }  
  16.    
  17.             }  
  18.             //如果没有获取到睡一会儿,再取锁  
  19.             Thread.sleep(1000);  
  20.         }  
  21.     }  
  22. }  

ReentrantLock中还有一个重要方法newCondition获取“条件队列”方法,其作用类似使用内置锁时Object的wait、notify、notifyAll。这部分内容比较多,在这里就不展开讲解,后面抽时间单独总结下。另外ReentrantLock还有一些其他辅助方法,都比较好理解,就不一一列举,在使用过程中自行查阅即可。

 

总结

 

 

最后再强调下显式锁Lock与内置锁是互补关系,有些场景下只能使用内置锁(比如对方法加锁);有些场景下只能使用Lock(比如 需要防止死锁、或者实现公平锁)。显式锁在java API中常用的还有读写锁ReentrantReadWriteLock,本次主要讲了重入锁ReentrantLock的实现原理和基本用法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值