Thread.interrupted()会清除当前线程的中断标记位。
整个获取锁的流程:
1.如果尝试获取锁成功,直接返回。
2.没成功,先加入到等待队列尾部,标记为独占模式。
3.尝试这获取一次锁后,如果还是获取不到就去休息,有机会时(轮到自己,会被unpark())会去尝试获取资源。获取到资源后才返回。如果在整个等待过程中被中断过,则返回true,否则返回false。
4.如果线程在等待过程中被中断过,它是不响应的。只是获取资源后才再进行自我中断selfInterrupt(),将中断补上。
这也就是ReentrantLock.lock()的流程
release(int)
此方法是独占模式下线程释放共享资源的顶层入口。它会释放指定量的资源,如果彻底释放了(即state=0),它会唤醒等待队列里的其他线程来获取资源。
public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); //唤醒等待队列里的下一个线程 return true; } return false; }
它是根据tryRelease()的返回值来判断该线程是否已经完成释放掉资源了!所以自定义同步器在设计tryRelease()的时候要明确这一点!!tryRelease(int) 此方法尝试去释放指定量的资源。
protected boolean tryRelease(int arg) { throw new UnsupportedOperationException(); }
还是需要AQS的实现类自己去写。 unparkSuccessor(Node) 此方法用于唤醒等待队列中下一个线程。
private void unparkSuccessor(Node node) { //这里,node一般为当前线程所在的结点。 int ws = node.waitStatus; if (ws < 0) //置零当前线程所在的结点状态,允许失败。 compareAndSetWaitStatus(node, ws, 0); //找到下一个需要唤醒的结点s 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)//从这里可以看出,<=0的结点,都是还有效的结点。 s = t; } if (s != null) LockSupport.unpark(s.thread);//唤醒 }
用unpark()唤醒等待队列中最前边的那个未放弃线程。 释放锁的流程 1.释放指定锁的资源并返回结果。 2.如果成功释放,就唤醒等待队列中最前边的那个未放弃线程。 3.如果没成功,返回false。
共享锁
public final void acquireShared(int arg) { if (tryAcquireShared(arg) < 0) doAcquireShared(arg); }
此方法用于将当前线程加入等待队列尾部休息,直到其他线程释放资源唤醒自己,自己成功拿到相应量的资源后才返回。
private void doAcquireShared(int arg) { final Node node = addWaiter(Node.SHARED); boolean failed = true; try { boolean interrupted = false; for (;;) { final Node p = node.predecessor(); if (p == head) { int r = tryAcquireShared(arg); if (r >= 0) {//成功 setHeadAndPropagate(node, r);//将head指向自己,还有剩余资源可以再唤醒之后的线程 p.next = null; // help GC if (interrupted) selfInterrupt(); failed = false; return; } } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } }
跟独占模式比,这里只有线程是head.next时(“老二”),才会去尝试获取资源,有剩余的话还会唤醒之后的队友。那么问题就来了,假如老大用完后释放了5个资源,而老二需要6个,老三需要1个,老四需要2个。老大先唤醒老二,老二一看资源不够,他是把资源让给老三呢,还是不让?答案是否定的!老二会继续park()等待其他线程释放资源,也更不会去唤醒老三和老四了。独占模式,同一时刻只有一个线程去执行,这样做未尝不可;但共享模式下,多个线程是可以同时执行的,现在因为老二的资源需求量大,而把后面量小的老三和老四也都卡住了。当然,这并不是问题,只是AQS保证严格按照入队顺序唤醒罢了(保证公平,但降低了并发)。 == setHeadAndPropagate(Node, int)==
private void setHeadAndPropagate(Node node, int propagate) { Node h = head; // Record old head for check below setHead(node); if (propagate > 0 || h == null || h.waitStatus < 0 || (h = head) == null || h.waitStatus < 0) { Node s = node.next; if (s == null || s.isShared()) doReleaseShared(); } }
== doReleaseShared()==
private void doReleaseShared() { for (;;) { Node h = head; if (h != null && h != tail) { int ws = h.waitStatus; if (ws == Node.SIGNAL) { if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) continue; // loop to recheck cases unparkSuccessor(h); } else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) continue; // loop on failed CAS } if (h == head) // loop if head changed break; } }
自定义锁
不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源state的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在顶层实现好了。自定义同步器实现时主要实现以下几种方法:
isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。 tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。 tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。 tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。 tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。
自定义一个简单的锁
/** * @author yhd * @createtime 2020/9/8 9:44 */ public class MLock implements Lock { private AbstractQueuedSynchronizer sync=new Sync(); @Override public void lock() { sync.acquire(1); } @Override public void lockInterruptibly() throws InterruptedException { } @Override public boolean tryLock() { return false; } @Override public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { return false; } @Override public void unlock() { sync.release(1); } @Override public Condition newCondition() { return null; } //自定义一个独占锁 private class Sync extends AbstractQueuedSynchronizer { @Override protected boolean tryAcquire(int arg) { if (compareAndSetState(0, 1)) { setExclusiveOwnerThread(Thread.currentThread()); return true; # 最后我们该如何学习? **1、看视频进行系统学习** 这几年的Crud经历,让我明白自己真的算是菜鸡中的战斗机,也正因为Crud,导致自己技术比较零散,也不够深入不够系统,所以重新进行学习是很有必要的。我差的是系统知识,差的结构框架和思路,所以通过视频来学习,效果更好,也更全面。关于视频学习,个人可以推荐去B站进行学习,B站上有很多学习视频,唯一的缺点就是免费的容易过时。 另外,我自己也珍藏了好几套视频资料躺在网盘里,有需要的我也可以分享给你: ![1年半经验,2本学历,Curd背景,竟给30K,我的美团Offer终于来了](https://ucc.alicdn.com/images/user-upload-01/img_convert/59569ae6c88695e0dc7cc749f66014b6.png) **2、读源码,看实战笔记,学习大神思路** “编程语言是程序员的表达的方式,而架构是程序员对世界的认知”。所以,程序员要想快速认知并学习架构,读源码是必不可少的。阅读源码,是解决问题 + 理解事物,更重要的:看到源码背后的想法;程序员说:读万行源码,行万种实践。 Spring源码深度解析: ![1年半经验,2本学历,Curd背景,竟给30K,我的美团Offer终于来了](https://ucc.alicdn.com/images/user-upload-01/img_convert/18aa1bf64527b019561188427118ca00.png) Mybatis 3源码深度解析: ![1年半经验,2本学历,Curd背景,竟给30K,我的美团Offer终于来了](https://ucc.alicdn.com/images/user-upload-01/img_convert/c94b3f2f286c7990cd2bf4e0d0cb9fc3.png) Redis学习笔记: ![1年半经验,2本学历,Curd背景,竟给30K,我的美团Offer终于来了](https://ucc.alicdn.com/images/user-upload-01/img_convert/e3e81600df834d35de63fad72242e32a.png) Spring Boot核心技术-笔记: ![1年半经验,2本学历,Curd背景,竟给30K,我的美团Offer终于来了](https://ucc.alicdn.com/images/user-upload-01/img_convert/b2c32afd4c7be63e65d8fb55f32abcb3.png) **3、面试前夕,刷题冲刺** 面试的前一周时间内,就可以开始刷题冲刺了。请记住,刷题的时候,技术的优先,算法的看些基本的,比如排序等即可,而智力题,除非是校招,否则一般不怎么会问。 关于面试刷题,我个人也准备了一套系统的面试题,帮助你举一反三: ![1年半经验,2本学历,Curd背景,竟给30K,我的美团Offer终于来了](https://ucc.alicdn.com/images/user-upload-01/img_convert/ecca26959f1f68f48e8cb86cd0301b53.png) 只有技术过硬,在哪儿都不愁就业,“万般带不去,唯有业随身”学习本来就不是在课堂那几年说了算,而是在人生的旅途中不间断的事情。 人生短暂,别稀里糊涂的活一辈子,不要将就。 **资料领取方式:[点击蓝色传送门免费领取上述资料](https://gitee.com/vip204888/java-p7)** 文章内容中涉及到的Java面试题、源码文档,技术笔记等学习资料,均可以免费分享给大家学习,只需你动动手多多支持即可! 源码深度解析: [外链图片转存中...(img-PbPbNROu-1628139638931)] Mybatis 3源码深度解析: [外链图片转存中...(img-BuaQYvto-1628139638933)] Redis学习笔记: [外链图片转存中...(img-Jr6q12s2-1628139638935)] Spring Boot核心技术-笔记: [外链图片转存中...(img-WGdImeQE-1628139638937)] **3、面试前夕,刷题冲刺** 面试的前一周时间内,就可以开始刷题冲刺了。请记住,刷题的时候,技术的优先,算法的看些基本的,比如排序等即可,而智力题,除非是校招,否则一般不怎么会问。 关于面试刷题,我个人也准备了一套系统的面试题,帮助你举一反三: [外链图片转存中...(img-q0aI0cq6-1628139638938)] 只有技术过硬,在哪儿都不愁就业,“万般带不去,唯有业随身”学习本来就不是在课堂那几年说了算,而是在人生的旅途中不间断的事情。 人生短暂,别稀里糊涂的活一辈子,不要将就。 **资料领取方式:[点击蓝色传送门免费领取上述资料](https://gitee.com/vip204888/java-p7)** 文章内容中涉及到的Java面试题、源码文档,技术笔记等学习资料,均可以免费分享给大家学习,只需你动动手多多支持即可!