并发编程:不得不说的ReentrantLock

从Lock讲起

Lock:一个接口,定义了在jdk层面上灵活实现锁的一种方式。
实现该接口的类是ReentrantLock。ReentrantLock这个单词的翻译是重入锁。

重入锁

持有锁的线程可以再次获取锁,增加重入次数,释放的锁时候也要将次数减为0。synchronized和ReentrantLock都是重入锁。

ReentrantReadWriteLock:重入读写锁,读和读可以共享,读和写、写和写存在互斥关系。

ReentrantLock的原理

加锁流程

一开始线程A调用lock方法抢占锁会通过CAS更新锁的状态State字段(0,1),假如更新成功则认为获取到了锁 --->
此时某个线程(可能是A自身)又调用了lock方法,此时会先去查看state:
假如等于0则直接去CAS抢锁;
不等于0,且当前线程已经是占有锁的线程,重入一次state+1;
不等于0,当前线程也没有持有锁---->
把当前节点封装成EXCLUSIVE NODE,初次加入链表时 自旋加入链表(先初始化一个空的头结点,并设置head和tail都指向该节点,初始化成功或者没初成功的线程 都去自旋更新尾节点);
封装到链表以后,假如prev是头结点,则再次尝试获取锁;
获取失败后检查前置节点的状态,如果不是SIGNAL,则自旋改为SIGNAL (-1),假如是SIGNAL,就可以挂起线程了。

解锁流程

CAS将state - 1,假如state减到0了(因为重入可能需要释放多次),表示真正地释放锁,并唤醒后一个节点。唤醒时会将当前节点status从SIGNAL改成0,此时后面的线程假如抢到了锁,会执行之前自旋获得锁方法后续部分---> 将头结点指向自己,将旧的头结点释放(old.next = null),并将当前节点变成空节点。此时就完成借节点的释放。

interrupt

注意在加锁和解锁的过程中,其他线程调用了被挂起线程的interrupt方法,被挂起的线程是无法立即响应的,该线程被唤醒后会查看interrupt状态,假如被interrupt过,就在之后调用线程的interrupt方法。

ReentrantLock和synchronize的区别

  • ReentrantLock是JUC包提供给我们的锁,synchronize是jvm级别的关键字
  • 释放:synchronize同步块执行结束或者抛异常;ReentrantLock调用unlock()方法
  • ReentrantLock可以灵活地控制锁,提供了公平锁
  • ReentrantLock有tryLock方法在阻塞时可以中断
  • ReentrantLock条件等待更灵活
posted @ 2020-07-12 15:33  挣扎一下  阅读(248)  评论(0编辑  收藏  举报