傻瓜源码-内容简介 |
---|
🤪【职场经验】(持续更新) 精编短文:如何成为值钱的Java开发-指南 如何平常学习、如何书写简历、引导面试官、系统准备面试、选择offer、提升绩效、晋升TeamLeader..... |
🧐【源码解读】(持续更新) <br/>1. 源码选材:Java架构师必须掌握的全部框架和类库源码<br/>2. 内容大纲:按照“企业应用Demo”讲解执行源码:总纲“阅读指南”、第一章“源码基础”、第二章“相关Java基础”、第三章“白话讲源码”、第四章“代码解读”、第五章“设计模式”、第六章“附录-面试习题、相关JDK方法、中文注释可运行源码项目” 3. 读后问题:粉丝群答疑解惑 |
已收录:HashMap、ReentrantLock、ThreadPoolExecutor、《Spring源码解读》、《Dubbo源码解读》..... |
🤩【面试题集】(持续更新)<br/>1. 面试题选材:Java面试常问的全部面试题和必会知识点<br/>2. 内容大纲:第一部分”注意事项“、第二部分“面试题解读”(包括:”面试题“、”答案“、”答案详解“、“实际开发解说”) 3. 深度/广度:面试题集中的答案和答案详解,都是对齐通常面试要求的深度和广度 4. 读后问题:粉丝群答疑解惑 |
已收录:Java基础面试题集、Java并发面试题集、JVM面试题集、数据库(Mysql)面试题集、缓存(Redis)面试题集 ..... |
🤤【粉丝群】(持续更新) <br/>收录:阿里、字节跳动、京东、小米、美团、哔哩哔哩等大厂内推 |
😛 做者介绍:Spring系源码贡献者、世界五百强互联网公司、TeamLeader、Github开源产品做者 😛 做者微信:wowangle03 (企业内推联系我) |
加入个人粉丝社群,阅读更多内容。从学习到面试,从面试到工做,从 coder 到 TeamLeader,天天给你答疑解惑,还能有第二份收入!html
本文建议分为两个学习阶段,掌握了第一阶段,再进行第二阶段;java
源码项目中的注释含义:node
ReentrantLock 是 java.util.concurrent 并发包中的可重入锁(可重入锁就是指持有锁的线程能够重复进入有该锁的代码块);基于 AQS(AbstractQueuedSynchronized)实现。须要手动释放锁,可是支持更多方法,好比:上公平/非公平锁、可被中断锁、可被中判定时锁等。面试
@Test public void testReentrantLock1() { // 多个线程使用同一个 ReentrantLock 对象,上同一把锁,默认上非公平锁 Lock lockNoFair = new ReentrantLock(); try { // 本线程尝试获取锁;若是锁已经被其它线程持有,则会进入阻塞状态,直到获取到锁 lockNoFair.lock(); System.out.println("处理中..."); } finally { // 释放锁 lockNoFair.unlock(); } } @Test public void testReentrantLock2() { // 多个线程使用同一个 ReentrantLock 对象,上同一把锁,构造函数传 true,上公平锁 Lock lockFair = new ReentrantLock(true); try { // 本线程尝试获取锁;若是锁已经被其它线程持有,则会进入阻塞状态,直到获取到锁 lockFair.lock(); System.out.println("处理中..."); } finally { // 释放锁 lockFair.unlock(); } }
1. lock()(公平锁)sql
线程获取锁的顺序彻底基于调用 lock() 方法的前后顺序。数据库
时间线 | 线程 1 | 线程 2 | 线程 3 |
---|---|---|---|
1 | 线程 1 调用 lockFair.lock() 方法,获取到锁 | ||
2 | 线程 2 调用 lockFair.lock() 方法后,没有获取到锁,将线程 2 顺序放入到链表里排队,进入阻塞状态 | ||
3 | 线程 1 调用 lockFair.unLock() 方法,释放锁,唤醒线程 2 | ||
4 | 线程 3 调用 lockFair.lock() 方法,发现线程 2 已经在链表里等待得到锁;线程 3 就追加到线程 2 以后,进行排队 | ||
5 | 线程 2 被线程 1 唤醒,从新尝试获取锁,获取锁成功 |
2. lock()(非公平锁)设计模式
时间线 | 线程 1 | 线程 2 | 线程 3 |
---|---|---|---|
1 | 调用 lockNoFair.lock() 方法,获取到锁 | ||
2 | 调用 lockNoFair.lock() 方法后,没有获取到锁,线程 2 顺序追加到链表后排队,进入阻塞状态 | ||
3 | 调用 lockNoFair.unLock() 方法,释放锁,唤醒线程 2 | ||
4 | 调用 lockNoFair.lock() 方法,成功获取到锁 | ||
5 | 线程 2 被线程 1 唤醒,呆在链表里位置不动,从新尝试获取锁,获取锁失败,已经被线程 3 抢占到了锁,再次进入阻塞状态 |
3. lockInterruptibly()(可被中断锁)缓存
lockInterruptibly() 也分为公平锁和非公平锁,与 lock() 方法的区别就在于:当线程调用 lockInterruptibly() 方法没有获取到锁,进入阻塞后;若是其它线程对该线程标记为中断状态, lockInterruptibly() 方法则会从阻塞中唤醒,抛出中断异常。安全
若是线程一开始就被标记为中断状态,再调用 lockInterruptibly() 方法,lockInterruptibly() 方法则会直接抛出中断异常。微信
4. tryLock(long timeout, TimeUnit unit) 方法(可被中判定时锁)
tryLock(long timeout, TimeUnit unit) 也区分公平锁和非公平锁;与 lockInterruptibly() 方法的区别就在于:线程调用 tryLock(long timeout, TimeUnit unit) 方法,获取不到锁,进入阻塞后;若是在指定的时间里,仍然没有被其它释放锁的线程唤醒,则会自动唤醒,直接返回失败。
知足如下几个特色,咱们就说这个操做支持原子性,线程安全:
解释:
包含多个操做单元,但仍支持原子性,一般都是由锁实现的。
代码示例:
class Test { int x = 0; int y = 0; public void test() { // 原子操做 x = 10; // 大体分为两步:1)获取 x 的值到缓存里;2)取出缓存里的值,赋值给 y // 不支持原子性;获取 x 的值到缓存里以后,其它线程可能修改 x 的值,致使 y 值错误 y = x; // 大体分为三步:1)获取 x 的值到缓存里;2)取出缓存里的值加一;3)赋值给 x // 不支持原子性;原理相似 y = x; x++; } }
Cas 是 Compare-and-swap(比较并替换)的缩写,是支持原子性的操做;在 Java 中,底层是 native 方法实现,经过 CPU 提供的 lock 信号保证的原子性。
想要将数据 V 的原值 O 替换为新值 N,执行 Cas 操做,会有如下操做:
执行 Cas 操做:
比较当前数据 V 的值是不是 O;
例子:
以 java.util.concurrent.atomic 包中的 AtomicInteger 为例;
public static void main(String[] args) { AtomicInteger atomicInteger = new AtomicInteger(100); // 将 AtomicInteger 的值,从 100 替换为 200 Boolean b = atomicInteger.compareAndSet(100, 200); // 返回 true,替换成功 System.out.println(b); }
用于修饰变量,能够保证被修饰变量的操做支持可见性和有序性,但不支持原子性。详见”Java 并发面试题集“
void interrupt():在一个线程中调用另外一个线程的 interrupt() 方法,会将那个线程设置成线程中断状态,而不会真的中断线程。
boolean isInterrupted(boolean ClearInterrupted):返回当前线程是否处于中断状态,ClearInterrupted 为 true,则返回的同时清除中断状态,反之则保留中断状态。
interrupt() 代码示例
class ThreadTest { public static void main(String[] args) throws Exception { MyThread thread = new MyThread(); thread.start(); // 主线程经过调用 thread 对象的 interrupt() 方法,将 thread 线程设置成线程中断状态,但不会真的中断线程。 thread.interrupt(); System.out.println("主线程执行结束!"); } } class MyThread extends Thread { @Override public void run() { System.out.println("test thread!"); } } // 打印结果: // 主线程执行结束! // test thread!
在 ReentrantLock 中,定义了一个内部抽象类 Sync;有两个实现类,分别为 FairSync(公平锁) 和 NonfairSync (非公平锁),是 ReentrantLock 里的真正实现锁逻辑的类。
代码示例 1 ReentrantLock 成员变量
public class ReentrantLock implements Lock, java.io.Serializable { private final Sync sync; abstract static class Sync extends AbstractQueuedSynchronizer { // state 表示锁的状态。int 类型未指定值,默认初始化为0; // 为 0 则表明当前没有线程持有锁;为 1 则表明有线程持有锁;若是大于1,则表明锁被当前线程重入的次数 // 继承自 AbstractQueuedSynchronizer private volatile int state; // exclusiveOwnerThread 表示当前持有锁的线程对象 // 继承自 AbstractOwnableSynchronizer private transient Thread exclusiveOwnerThread; // tail 记录尾线程节点对象,默认为 null // 继承自 AbstractQueuedSynchronizer private transient volatile Node tail; // head 记录头线程节点对象,默认为 null // 继承自 AbstractQueuedSynchronizer private transient volatile Node head; }
ReentrantLock 使用 FIFO (先进先出)队列来管理竞争锁的线程关系。
在 ReentrantLock 中, Sync 经过继承了 AQS 抽象类(AbstractQueuedSynchronizer),进而继承 AbstractQueuedSynchronizer 中的内部类 Node,用来封装线程,同时也是链表的组成元素。
代码示例 Node 重要成员变量
static final class Node { // prev 指向前一个节点(表明前一个进入链表的线程) volatile Node prev; // next 指向后一个节点(表明后一个进入链表的线程) volatile Node next; // thread 当前节点表示的线程 volatile Thread thread; // 0:表示[初始默认值]或者[表示已解锁] // -1(SIGNAL):表示当前节点表明的线程在释放锁后须要唤醒下一个节点的线程 // 1(CANCELLED):表示当前节点表明的线程在队列中发生异常(发生中断异常或者其它不可预知的异常),标记为取消状态 volatile int waitStatus;
初始状态:head 和 tail 变量为空,不存在链表。
初始化不表明线程的头节点,再在头节点后插入线程 1 节点(线程 1 获取锁失败,放入链表,等待获取锁):
而后再为线程 1 新建一个 Node 节点对象 node1,追加到头节点后面:
线程 1 节点替换成头节点(线程 1 在链表中被线程 0 唤醒,成功获取到锁):
<br/>
加入个人粉丝社群,阅读所有内容
从学习到面试,从面试到工做,从 coder 到 TeamLeader,天天给你答疑解惑,还能有第二份收入,这样的知识星球,难道你还要犹豫!