1.Reentrantlock那些常用的方法

lock()方法
unlock()方法
lockInterruptibly()方法

2.等待队列图解

说起lock方法,其实lock() 和 unlock()相当于synchronize的左花括号和右花括号,但是当我们用到lock()的时候,需要手动的进行再合适的地方unlock()这样的话才能够释放锁,而synchronize到右花括号结束的话就释放锁。
下来我们就看看线程之间是如果争夺这个锁的。
先设一个前提,有三个线程都想要获取同一把锁进行对应的操作,Reentrantlock背后是怎样处理的

public class ReentrantLock implements Lock, java.io.Serializable {private static final long serialVersionUID = 7373984872572414699L;private final Sync sync;public ReentrantLock() {//无参的初始化默认为非公平同步sync = new NonfairSync();}public ReentrantLock(boolean fair) {//对于我们进行初始化的时候//Sync类继承了AQS类//FairSync类继承了Sync 为公平锁//NonfairSync继承了Sync  为非公平锁sync = fair ? new FairSync() : new NonfairSync();}public void lock() {//sync.lock();}public void lockInterruptibly() throws InterruptedException {sync.acquireInterruptibly(1);}public void unlock() {sync.release(1);}
}

其实在Reentrantlock中的代码很简单,但是不止我上边所展示的这些。
从上边的代码看。其实不管是这三个方法的哪一个方法,都离不开Sync,其实Sync是Reentrantlock的一个静态内部类。
Sync 继承了 AQS,这说明对于真正Reentrantlock可以做到同步的效果AQS这个类真是功不可没,并且成员sync也是有一定的分量。

剧透
对于同一个锁来说,也相当于是同一个Reentrantlock对象来说,
这个对象中维持一个等待队列,确切的说是背后的AQS对象维持了一个等待队列,Reentrantlock是依靠AQS的。这个等待队列是一个双向的链表。AQS通过一个 volitile int state 来保证线程之间的同步。
先康康AQS中的主要成员。

public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizerimplements java.io.Serializable {private transient volatile Node head;private transient volatile Node tail;private volatile int state;

由于AQS类继承了AOS,所以AQS也相当于拥有AOS类的成员。

public abstract class AbstractOwnableSynchronizer implements java.io.Serializable {private transient Thread exclusiveOwnerThread;
}

插曲公平锁 和 非公平锁

我觉得例子总是让我们清晰易懂。
脑补此时有三个线程,想要获取同一个锁,如果第一个线程抢占到CPU资源,那么第一个线程会通过lock方法首先拥有锁,拥有锁体现为AQS中的int state变量会变为1.

当线程一的时间片段到了,此时线程一并没有运行到unlock方法,那就说明此时的锁没有被释放,同时也体现在AQS中的int state变量没有被更改回0.

此时线程二和线程三依次运行起来,检查AQS中int state的值为1,说明锁被其他线程所持有,那么线程二和三则被加入到AQS维持的等待队列中。

1.公平锁:
如果此时线程一释放了锁,其实释放锁的同时会唤醒等待队列中最老的线程(此时为线程二),此时线程二依旧在等待队列中保存,只有线程二获取到锁,才会从等待队列中删除。
那么此时如果线程一和线程二同时竞争CPU资源,线程一竞争到了cpu资源,又开始获取锁,对于公平锁来说,那当然要维持公平了,所以线程一在获取锁的过程中,程序先检查等待队列中是否有线程正在等待,如果有的话,先看最老的等待线程是否和当前想要获取锁的线程是同一个线程,很明显此时最老线程为线程二,当前线程为线程一,所以会直接将当前的线程一阻塞起来并加入到等待队列中(加入到线程三的后面)。
线程二此时因为已经被唤醒了,只是正等待竞争到cpu然后获取锁,在获取锁后就将自己在等待队列中删除。
公平锁相当于,只要是在等待队列中的线程都有获取锁的机会。
优点:所有的线程都能够得到资源,不会产生饥饿的状态
缺点:吞吐量会下降,因为不管有几个线程进行竞争,只要不是等待队列中的等待的线程,先加入到等待队列中,只允许按照申请锁的顺序进行获取锁,cpu唤醒等待线程的开销会增大

2.非公平锁:
看完上边公平锁,非公平锁也很好理解,当线程一再次想要获取锁的时候,程序不管等待队列的是否还有其他从未获取过锁的线程在等待,只要线程一再次抢占到cpu资源可以再次拥有锁。
此时就必得是谁能先抢占CPU的资源。这样就可能导致等待中的某些线程一直处于等待的状态,甚至没有获得锁的机会。
优点:可以减少CPU唤醒线程的开销,
缺点:会导致有些等待线程饿死的现象,申请的早,但迟迟没有机会获取锁。

下来我们就具体说说这个lock的方法和unlock方法(我拿非公平锁做例子
先看这个一层层的调用关系,你们可以对照源码比较方便,同时也是展示有用的代码,其他就不说了。

一.lock():

public class ReentrantLock implements Lock, java.io.Serializable { public void lock() {sync.lock();}

这边的sync.lock()调用的是NonfairSync中的lock()方法;NonfairSync是Reentrantlock的静态内部类。

static final class NonfairSync extends Sync {final void lock() {//采用CAS操作将AQS中的state变量进行修改为1,表明当前线程已经拥有锁if (compareAndSetState(0, 1))//将AQS中的exclusiveOwnerThread变量设置为当前的线程setExclusiveOwnerThread(Thread.currentThread());else//对于如果其他线程持有锁,而其当前的线程也想要获取锁的话//当前线程会运行到这里,因为AQS中的state状态已经被其他持有锁//的线程从0修改为1,当前线程只能去尝试获取锁。acquire(1);}}
acquire();
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizerimplements java.io.Serializable {public final void acquire(int arg) {//tryAcquire方法是由NonfairSync类进行实现的。//tryAcquire方法中进行AQS中状态的获取,// 如果是0,表明现在没有任何线程持有锁,那么会将AQS中的状态改为1.并将AQS中的exclusiveOwnerThread设为当前执行的线程。并且返回true。  //如果是1,表明已经有线程持有锁了,那判断AQS中的exclusiveOwnerThread是否是当前线程,如果是当前线程,那此时表明当前线程重入,那么只是将AQS的状态加1,返回true//如果不是当前运行的线程,那说明当前线程想要获取锁,而此时锁已经被占有那只能返回false。表明当前线程获取锁失败。if (!tryAcquire(arg) &&//当前线程试图想要通过tryAcquire(arg)获取锁acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();//当tryAcquire(arg)返回值为false时,有必要进行acquireQueued()的判断//返回false说明获取锁失败,那么就要对此种情况进行处理。//addWaiter(Node.EXCLUSIVE)方法就是将当前线程形成一个Node节点放入AQS维持等待队列中,返回值为Node。//在加入到队列的过程中,如果队列为空,表示AQS中的head 和tail的值为null,AQS也是通过head和tail来维持这个等待队列的。//同时也说明当前线程是第一个加入到等待队列中的,等待队列是一个带头节点的双向链表,//那么此时会先生成一个空的node作为头节点,然后再将当前线程所形成的节点加入头节点之后,形成这个等待队列.// acquireQueued()方法将当前的Node节点中的线程,也就是当前线程阻塞起来。//具体的过程看下面两个方法具体过程}
}
Node节点主要成员
static final class Node {static final int CANCELLED =  1;static final int SIGNAL    = -1;static final int CONDITION = -2;static final int PROPAGATE = -3;    volatile int waitStatus;volatile Node prev;volatile Node next;volatile Thread thread;Node nextWaiter;
}
acquireQueued()
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizerimplements java.io.Serializable {final boolean acquireQueued(final Node node, int arg) {boolean failed = true;try {boolean interrupted = false;for (;;) {//p为线程所包装的节点的前驱节点,因为在addwaiter的方法中已经将当前线程//包装为一个Node几点并且加入到等待队列中了//p可能为空的头节点,也可能为已经加入到等待队列中的其他线程所构成的节点final Node p = node.predecessor();//如果为头节点,表明当前线程是所构成的节点是第一个加入到等待队列中的,此时由机会去获取锁,//为什么这么说呢,跟排队一样当然是第一个排队的人最可能先得到服务//这里也一样,如果此时是等待队列中的第一个,那么有机会进行tryAcquire(arg),也就是锁的获取操作,如果成功获取锁,那就说明当前线程所形成的节点不用在等待队里中了,那么会进行一次队列删除操作。if (p == head && tryAcquire(arg)) {setHead(node);p.next = null; // help GCfailed = false;return interrupted;}//当当前线程获取锁没有成功时,就会进入到下面的判断,// shouldParkAfterFailedAcquire(p, node)这个方法主要是来判断当前线程是否具备阻塞的条件,//新节点刚加入的时候是不满足阻塞条件的,那么此时会再次循环//此时就会跑到上面的if判断,相当于又给了线程一个次获得锁的机会,当for循环第二次到 shouldParkAfterFailedAcquire(p, node)时候,此时当前线程就满足了阻塞条件,//因为第一次的时候shouldParkAfterFailedAcquire(p, node),将新加入的节点的前驱节点的状态改为SIGNAL,返回false//第二次的时候检查当前节点节点的前驱节点状态已经为SIGNAL,所以返回true。//只要当前节点的前驱节点的状态为SIGNAL,就表明当前线程可以进行阻塞。if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())// parkAndCheckInterrupt()方法就是当shouldParkAfterFailedAcquire(p, node)方法返回true的时候就执行,执行的内容就是将当前线程进行阻塞,如果当前线程设置类中断标志,那么当该线程被唤醒的时候返回true//如果没有设置中断标志,那么该线程被唤醒的时候返回false//并且当前线程已经形成节点在等待队列中了,就等着其他线程释放锁,在等待队里中国唤醒自己。interrupted = true;}} finally {if (failed)cancelAcquire(node);}}
}
shouldParkAfterFailedAcquire(p, node)
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizerimplements java.io.Serializable {private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {int ws = pred.waitStatus;//得到当前线程所在节点的前驱节点的状态,if (ws == Node.SIGNAL)//如果前驱节点的状态为SIGNAL(-1)状态,说明此事该线程的阻塞条件达到,//因为在之后在释放锁的时候会在等待队列中去唤醒头节点的的后驱节点//每一个等待节点都有可能变为头节点,在下图会看的更详细//所以当前节点的前驱节点的状态应该是SIGNAL状态,因为这样在之后能够根据它的前驱节点为SIGNAL而唤醒当前节点。return true;if (ws > 0) {//ws大于0表明前驱节点的状态为CANCELLED状态,表明为取消状态do {node.prev = pred = pred.prev;} while (pred.waitStatus > 0);pred.next = node;} else {//表示前驱节点为状态为0;//因为等待队列中的最后一个节点状态始终为0;//所以对新加入到等待队列中的节点来说它的前驱节点为当前队列中的最后一个节点//对于新加入的节点首次进入shouldParkAfterFailedAcquire方法//首先通过下面的这个方法将前驱节点的状态设置为SIGNAL状态。compareAndSetWaitStatus(pred, ws, Node.SIGNAL);}return false;}

二.Unlock方法()

 public void unlock() {sync.release(1);}
release(int)
 public final boolean release(int arg) {if (tryRelease(arg)) {//进行锁的释放,更改AQS中的state,和拥有锁的线程Node h = head;//此时的head就是等待队列的头节点//每次当一个线程释放自己有的锁的时候,//总是唤醒该锁等待队列中那个最早等待的,也就是头节点的后驱节点if (h != null && h.waitStatus != 0)unparkSuccessor(h);return true;}return false;}

/对于释放锁来说,如果一个线程产生了重入锁的情况那么
下面方法的getstate就不太是1. 可能会是2,3,4…

tryRelease(int)
protected final boolean tryRelease(int releases) {//getstate得到AQS中的state的值,如果某一线程只获得过一次锁,那么此时该线程想要释放锁的话c = 1 -1 = 0;
//如果之前某一线程产生了重入锁的情况,那么此时getstate不再是1,
//所以对于重入锁的情况来说,现线程重入锁几次,就要释放锁几次,不然在那些等待队列中的线程是无法被唤醒的
//当某一线程只获得过一次锁,那么该线程此时释放锁的的话,c = 0int c = getState() - releases;if (Thread.currentThread() != getExclusiveOwnerThread())throw new IllegalMonitorStateException();boolean free = false;if (c == 0) {free = true;setExclusiveOwnerThread(null);//将AQS中将保存之前持有锁的线程的变量赋值为null。}//并且将AQS的state改为c的值setState(c);return free;}
unparkSuccessor(Node)
private void unparkSuccessor(Node node) {//参数为头节点int ws = node.waitStatus;if (ws < 0)//表示头节点的状态为SIGNAL,将头节点的状态改为0compareAndSetWaitStatus(node, ws, 0);Node s = node.next;//得到头节点的后驱节点if (s == null || s.waitStatus > 0) {s = null;for (Node t = tail; t != null && t != node; t = t.prev)if (t.waitStatus <= 0)s = t;}if (s != null)//当头节点有后驱节点的时候,那就唤醒后驱节点中的线程LockSupport.unpark(s.thread);}

三. lockInterruptibly()

对于lockInterruptibly和lock来说不一样的一点在于lockInterruptibly将lock调用过程的的acquireQueued()方法换成了doAcquireInterruptibly方法。

采用lockInterruptibly方法进行获取锁,表明的是可以处理中断,意思很简单,就是两个线程都想要获取同一个锁,另外一个线程获取了锁,而当前的线程由于没有获取锁而加入到了等待队列,但是如果当前的线程被别的线程设置了Thread.interrupt。
那么此时当前被唤醒的性质就不是说由于某一个线程释放锁而被唤醒,而是因为中断被唤醒,所以在 doAcquireInterruptibly(int arg)方法中if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())成立之后,
那么此时这个线程被唤醒后就直接
throw new InterruptedException();由外部处理,说明当前线程放弃了获取锁。

   private void doAcquireInterruptibly(int arg)throws InterruptedException {final Node node = addWaiter(Node.EXCLUSIVE);boolean failed = true;try {for (;;) {final Node p = node.predecessor();if (p == head && tryAcquire(arg)) {setHead(node);p.next = null; // help GCfailed = false;return;}if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())//当由于中断唤醒,则直接抛出异常throw new InterruptedException();}} finally {if (failed)cancelAcquire(node);}}

应用例子:

public class Interrupt implements Runnable{private static final Lock lock = new ReentrantLock();private volatile int selected;public Interrupt(int select) {this.selected = select;}public void run() { if(selected == 0) {try { lock.lockInterruptibly();for(int i = 0; i < 1000; i++) {} lock.unlock();     }catch (InterruptedException e) {System.out.println("线程一说:我不想等了");}}else if(selected == 1){try {lock.lockInterruptibly();Thread.sleep(1000);lock.unlock();ystem.out.println("线程二说: 锁我用完了");} catch (InterruptedException e) {}}}
public class Test {public static void main(String[] args) {Thread one = new Thread(new Interrupt(0));Thread two = new Thread(new Interrupt(1));two.start();one.start();  one.interrupt();

2.等待队列的图解


Reentrantlock和背后金主AQS —————— 开开开山怪相关推荐

  1. 阿里、京东、美团……背后的共同金主,他拿下了中国互联网半壁江山!

    Python实战社群 Java实战社群 长按识别下方二维码,按需求添加 扫码关注添加客服 进Python社群▲ 扫码关注添加客服 进Java社群▲ 作者:电商君 来源:电商报(ID:kandiansh ...

  2. “金主爸爸快回来交学费吧!”疫情让中国留学生难以返校,国外高校面临资金短缺...

    浩楠 发自 凹非寺  量子位 报道 | 公众号 QbitAI 留学生无法毕业,学校难以为继. 疫情冲击的不只是工业贸易,海外高校和留学生正面临着前所未有的艰难局面. 现在大约有10万中国留学生被迫滞留 ...

  3. 那些赞助世界杯的金主,正在变成赌徒

    文/羿雪琴 来源 | 潇湘财经(XiaoxiangFin) 毫无意外,2018年俄罗斯世界杯圆满结束了. 但又全是意外,德国.阿根廷.葡萄牙.西班牙等豪门强队相继"排队上天台",让 ...

  4. 成功的背后!(版主吐血推荐)

    成功的背后!(版主吐血推荐) 成功的背后!(给所有IT人) 一篇很好的文章,建议想成功的IT人士一气读完吆!肯定有所收获的,特别对IT刚入行的新人! 成功的背后,有着许多不为人知的故事,而正是这些夹杂 ...

  5. 百度的广告金主需要MarTech产品不?

    我跟你说:百度前六类金主是:医美与培训.APP与游戏.电商与金融.地产与汽车.他们支撑了百度一年700多亿的广告营收,就多琢磨琢磨他们投了百度搜索竞价广告后,后续做什么,需要什么工具. 当然,很多人对 ...

  6. Qbao Network 1024周年庆, 邀请Q 宝宝和金主爸爸来抱锦鲤啦!

    Hi 币圈的小伙伴们,10月24日是Qbao Network 的生日哦!自10月24日起,Qbao Network 将举办为期一周的周年庆活动!上周五小Q 已在各个渠道发布这个消息了,不知大家有没有收 ...

  7. 谷歌、微软、Meta?谁才是 Python 最大的金主?

    你知道维护 Python 这个大规模的开源项目,每年需要多少资金吗? 答案是:约 200 万美元! PSF(Python 软件基金会)在 2022 年 6 月发布了 2021 的年度报告,其中披露了以 ...

  8. 能爬旅游景点数据的知名网站_某知名网站公布年度数据,十大吸金主播斗鱼独占前四,小团团终成“女幻神”,339斩获一哥...

    转眼间斗鱼直播平台又陪伴大家度过了愉快的一年,如今回顾整个2019年,我们收获了很多快乐,与此同时创下无数个记录!比如在知名数据网站"头榜"近日发布的"2019头榜星势力 ...

  9. Facebook、谷歌、苹果最新财报连连看,“金主”非苹果莫属

    近来,Facebook.谷歌.苹果先后发布了各自的季度财报,让我们一起来了解一下他们的财报数据吧,让我们通过财报来认识谁才是真正的"金主". 一.Facebook Facebook ...

  10. 聚观早报 |中国企业成世界杯最大金主;马斯克恐失去世界首富位置

    今日要闻:中国企业成世界杯最大金主:马斯克恐失去世界首富位置:刘强东称对不起京东高管:苹果在沪最大代工厂重启招工:威马汽车上海全员工资打折 中国企业成世界杯最大金主 据报道,作为全球最为瞩目的赛事之一 ...

最新文章

  1. SpringBoot第十六篇:用restTemplate消费服务
  2. 第 5 章 Stream
  3. Java 中的 BigDecimal,你真的会用吗?
  4. OpenCV3.0中的离散傅里叶变换
  5. 渐变色 + 屏幕缩小自动产生滚动条
  6. Splay模板 1.0
  7. shell 调试脚本设置
  8. 华为微博抽奖头目两次中奖:大哥咱玩不起,不玩行不行?
  9. 解决 Hbuilder打包 Apk pad 无法横屏 以及 H5 直接打包 成Apk
  10. 华为虚拟机eNSP命令大全
  11. Android 面试中高级上
  12. python 字符串前面加‘r’的作用(并举例说明)
  13. redis 学习曲线及记录笔记
  14. php 应用截图,PHP应用:php使用CutyCapt实现网页截图保存的方法
  15. 你可能不知道的canvas烟花秀
  16. CF1238E Keyboard Purchase
  17. 彻底解决:fake_useragent.errors.FakeUserAgentError: Maximum amount of retries reached
  18. 银行业务细分,商业银行业务学习
  19. 大数据在城市规划领域的应用思考
  20. 法拉第PK特斯拉,美产与国产谁能取胜?

热门文章

  1. Bug软件缺陷管理制度
  2. excel建立层级_利用Excel升职加薪——数据分析报告
  3. 炼数成金《数据分析与SAS》课程
  4. 《黑白团团队》第六次作业:团队项目系统设计改进与详细设计
  5. java 众数 中位数_什么是中位数、众数、平均数,今天终于弄明白
  6. 有两个瓶子,一个瓶子是5升_隐藏的功能,装在瓶子中的消息和痴呆的面包师。...
  7. python空气质量提醒代码_【python】全国空气质量分析系统
  8. 项目配置文件----.eslintignore,eslint在做风格检查的时候忽略 dist 和 vender(第三方库) 不去检查。
  9. 《谈谈方法》这本小书篇幅很短,然而想说的却很多
  10. php文件是不是死链,怎么判断网站的链接是不是死链接? 百度搜索标准死链官方文档...