2020之最专业的并发编程篇:知识图鉴+知识点剖析+高频面试+书籍

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2020之最专业的并发编程篇:知识图鉴+知识点剖析+高频面试+书籍相关的知识,希望对你有一定的参考价值。

知识图鉴(我真的尽力清晰了)

技术图片

知识点剖析

1、JAVA 并发知识库

技术图片
2、JAVA 线程实现/创建方式

技术图片
3、4 种线程池

Java 里面线程池的顶级接口是 Executor,但是严格意义上讲 Executor 并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是 ExecutorService。

技术图片
4、线程生命周期(状态)

当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中,它要经过新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)5 种状态。尤其是当线程启动以后,它不可能一直"霸占"着 CPU 独自运行,所以 CPU 需要在多条线程之间切换,于是线程状态也会多次在运行、阻塞之间切换

5、终止线程 4 种方式

6、sleep 与 wait 区别

7、start 与 run 区别

8、后台线程

9、JAVA 锁

10、线程基本方法

11、线程上下文切换

12、同步锁与死锁

13、线程池原理

14、JAVA 阻塞队列原理

15、…………

技术图片

高频面试题

Synchronized 相关问题

问题一: Synchronized 用过吗, 其原理是什么?

这是一道 Java 面试中几乎百分百会问到的问题, 因为没有任何写过并发程序的开发者会没听说或者没接触过 Synchronized。

Synchronized 是由 JVM 实现的一种实现互斥同步的一种方式, 如果你查看被Synchronized 修饰过的程序块编译后的字节码, 会发现, 被 Synchronized 修饰过的程序块, 在编译前后被编译器生成了monitorenter 和 monitorexit 两个字节码指令。

这两个指令是什么意思呢?

在虚拟机执行到 monitorenter 指令时, 首先要尝试获取对象的锁: 如果这个对象没有锁定, 或者当前线程已经拥有了这个对象的锁, 把锁的 计 数 器

+1; 当 执 行 monitorexit 指 令 时 将 锁 计 数 器

-1; 当 计 数 器为 0 时 , 锁 就 被 释 放 了 。

如果获取对象失败了, 那当前线程就要阻塞等待, 直到对象锁被另外一个线程释放为止。Java中Synchronize通 过 在 对 象 头 设 置 标 记 , 达 到 了 获 取 锁 和 释 放锁 的 目 的 。

问题二: 你刚才提到获取对象的锁, 这个“ 锁” 到底是什么? 如何确定对象的锁?

“ 锁” 的本质其实是 monitorenter 和 monitorexit 字节码指令的一个 Reference

类型的参数, 即要锁定和解锁的对象。 我们知道, 使用Synchronized 可以修饰不同的对象, 因此, 对应的对象锁可以这么确定。

  1. 如果 Synchronized 明确指定了锁对象, 比如 Synchronized( 变量名) 、 Synchronized( this) 等, 说明加解锁对象为该对象。

  2. 如 果 没 有 明 确 指 定 :

若 Synchronized 修饰的方法为非静态方法, 表示此方法对应的对象为锁对象;

若 Synchronized 修饰的方法为静态方法, 则表示此方法对应的类对象为锁对象。

注意, 当一个对象被锁住时, 对象里面所有用 Synchronized 修饰的方法都将产生

堵塞, 而对象里非 Synchronized 修饰的方法可正常被调用, 不受锁影响。

问题三: 什么是可重入性, 为什么说 Synchronized 是可重入锁?
可 重 入 性是 锁 的 一 个 基 本 要 求 , 是 为 了 解 决 自 己 锁 死 自 己 的 情 况 。 比 如下面的伪代码, 一个类中的同步方法调用另一个同步方法, 假如Synchronized 不 支 持 重 入 , 进 入 method 2 方 法 时 当 前 线 程 获 得 锁 ,method 2 方法里面执行 method 1 时当前线程又要去尝试获取锁, 这时如果不支持重入, 它就要等释放, 把自己阻塞, 导致自己锁死自己。对 Synchronized 来说, 可重入性是显而易见的, 刚才提到, 在执行monitorenter 指令时, 如果这个对象没有锁定, 或者当前线程已经拥有了这个对象的锁( 而不是已拥有了锁则不能继续获取) , 就把锁的计数器 + 1 ,其实本质上就通过这种方式实现了可重入性。

问题四: JVM 对 Java 的原生锁做了哪些优化?

在 Java 6 之 前 , Monitor 的 实 现 完 全 依 赖 底 层 操 作 系 统 的 互 斥 锁 来实 现 , 也 就 是 我 们 刚 才 在 问 题 二 中 所 阐 述 的 获 取 /释 放 锁 的 逻 辑 。由 于 Java 层 面 的 线 程 与 操作 系 统 的 原 生 线 程 有 映 射 关 系 , 如 果 要 将 一个 线 程 进 行 阻 塞 或 唤 起 都 需 要 操 作 系 统 的 协 助 , 这 就 需 要 从 用 户 态 切 换到 内 核 态 来 执 行 , 这 种 切 换 代 价 十 分 昂 贵 , 很 耗 处 理 器 时 间 , 现 代 JDK中 做 了 大 量 的 优 化 。一种优化是使用自旋锁, 即在把线程进行阻塞操作之前先让线程自旋等待一段时间,可能在等待期间其他线程已经解锁, 这时就无需再让线程执行阻塞操作, 避免了用户态到内核态的切换。现代 JDK 中还提供了三种不同的Monitor 实现, 也就是三种不同的锁:

? 偏 向 锁 ( Biased Locking)

? 轻量级 锁

? 重量级 锁

这 三 种 锁 使 得 JDK 得 以 优 化 Synchronized 的 运 行 , 当 JVM 检 测到 不 同 的 竞 争 状 况 时 , 会 自 动 切 换 到 适 合 的 锁 实 现 , 这 就 是 锁 的 升 级 、降 级 。

? 当没有 竞争出现 时, 默认会 使用偏向 锁。JVM 会 利 用 CAS 操 作 , 在 对 象 头 上 的 Mark Word 部 分 设 置 线 程ID, 以 表 示 这 个 对 象 偏 向 于 当 前 线 程 , 所 以 并 不 涉 及 真 正 的 互 斥 锁 , 因为 在 很 多 应 用 场 景 中 , 大 部 分 对 象 生 命 周 期 中 最 多 会 被 一 个 线 程 锁 定 ,使 用 偏 斜 锁 可 以 降 低 无 竞 争 开 销 。

? 如 果 有 另 一 线 程 试 图 锁 定 某 个 被 偏 斜 过 的 对 象 , JVM就 撤 销 偏 斜 锁 ,切 换 到 轻 量 级 锁 实 现 。

? 轻量级 锁依赖 CAS 操作 Mark Word 来试图获取锁, 如果重试成功, 就使用普通的轻量级锁; 否则, 进一步升级为重量级锁。

问题五: 为什么说 Synchronized 是非公平锁?

非公平主要表现在获取锁的行为上, 并非是按照申请锁的时间前后给等待线程分配锁的, 每当锁被释放后, 任何一个线程都有机会竞争到锁, 这样做的目的是为了提高执行性能, 缺点是可能会产生线程饥饿现象。

问题六: 什么是锁消除和锁粗化?

1.锁消除: 指虚拟机即时编译器在运行时, 对一些代码上要求同步, 但被检测 到 不 可 能 存 在 共 享 数 据 竞 争 的 锁 进 行 消 除 。 主 要 根 据 逃 逸 分 析 。 程 序员 怎 么 会 在 明 知 道 不 存 在 数 据 竞 争 的 情 况 下 使 用 同 步 呢 ? 很 多 不 是 程 序员 自 己 加 入 的 。

2.锁粗化: 原则上, 同步块的作用范围要尽量小。 但是如果一系列的连续操作 都 对 同 一 个 对 象 反 复 加 锁 和 解 锁 , 甚 至 加 锁 操 作 在 循 环 体 内 , 频 繁 地进 行 互 斥 同 步 操 作 也 会 导 致 不 必 要 的 性 能 损 耗 。锁粗化就是增大锁的作用域。

问题七: 为什么说Synchronized是一个悲观锁? 乐观锁的实现原理又是什么? 什么是CAS, 它有什么特性?

Synchronized 显然是一个悲观锁, 因为它的并发策略是悲观的:

不管是否会产生竞争, 任何的数据操作都必须要加锁、 用户态核心态转换、 维护锁计数器和检查是否有被阻塞的线程需要被唤醒等操作。随着硬件指令集的发展, 我们可以使用基于冲突检测的乐观并发策略。先进行操作,如果没有其他线程征用数据, 那操作就成功了;

如 果 共 享 数 据 有 征 用 , 产 生 了 冲 突 , 那 就 再 进 行 其 他 的 补 偿 措 施 。 这 种乐 观 的 并 发 策 略 的 许 多 实 现 不 需 要 线 程 挂 起 , 所 以 被 称 为 非 阻 塞 同 步 。乐 观 锁 的 核 心 算 法 是 CAS( Compareand Swap, 比 较 并 交 换 ) , 它 涉及 到 三 个 操 作 数 : 内 存 值 、 预 期 值 、 新 值 。 当 且 仅 当 预 期 值 和 内 存 值 相等 时 才 将 内 存 值 修 改 为 新 值 。

这样处理的逻辑是, 首先检查某块内存的值是否跟之前我读取时的一样, 如不一样则表示期间此内存值已经被别的线程更改过, 舍弃本次操作, 否则说明期间没有其他线程对此内存值操作, 可以把新值设置给此块内存。

CAS 具有原子性, 它的原子性由 CPU 硬件指令实现保证, 即使用JNI 调用Native 方法调用由 C++ 编写的硬件级别指令, JDK 中提供了 Unsafe 类执行这些操作。

问题八: 乐观锁一定就是好的吗?

乐观锁避免了悲观锁独占对象的现象, 同时也提高了并发性能, 但它也有缺点:

  1. 乐观锁 只能保证 一个共享 变量的原 子操作。 如果多 一个或几 个变量, 乐观锁将变得力不从心, 但互斥锁能轻易解决, 不管对象数量多少及对象颗粒度大小。

  2. 长 时 间 自 旋 可 能 导 致 开 销 大 。 假 如 CAS 长 时 间 不 成 功 而 一 直 自 旋 , 会给 CPU 带 来 很 大 的 开 销 。

  3. ABA 问 题 。 CAS 的 核 心 思 想 是 通 过 比 对 内 存 值 与 预 期 值 是 否 一 样 而 判断 内 存 值 是 否 被 改 过 , 但 这 个 判 断 逻 辑 不 严 谨 , 假 如 内 存 值 原 来 是A,后来被一条线程改为 B, 最后又被改成了 A, 则 CAS 认为此内存值并没有发生改变, 但实际上是有被其他线程改过的, 这种情况对依赖过程值的情景的运算结果影响很大。 解决的思路是引入版本号, 每次变量更新都把版本号加一。

可 重 入 锁 ReentrantLock 及 其 他 显 式 锁 相 关 问 题:

问题一: 跟Synchronized相比, 可重入锁Reentrant Lock其实现原理有什么不同?

问题二: 那么请谈谈 AQS 框架是怎么回事儿?

问题三: 请尽可能详尽地对比下Synchronized和Reentrant Lock的异同。

问题四: Reentrant Lock 是如何实现可重入性的?

问题五: 除了 Reetrant Lock, 你还接触过 JUC 中的哪些并发工具?

问题六: 请谈谈 Read Write Lock 和 Stamped Lock。

问题七: 如何让 Java 的线程彼此同步? 你了解过哪些同步器? 请分别介绍下。

问题八: Cyclic Barrier和Count Down Latch 看起来很相似, 请对比下呢?

Java 线程池相关问题

问题一: Java 中的线程池是如何实现的?

问题二: 创建线程池的几个核心构造参数?

问题三: 线程池中的线程是怎么创建的? 是一开始就随着线程池的启动创建好的吗?

问题四: 既然提到可以通过配置不同参数创建出不同的线程池, 那么Java 中默认实现好的

线程池又有哪些呢? 请比较它们的异同。

问题五: 如何在 Java 线程池中提交线程?

Java 内存模型相关问题
问题一: 什么是 Java 的内存模型, Java中各个线程是怎么彼此看到对方的变量的?

问题二: 请谈谈 volatile 有什么特点, 为什么它能保证变量对所有线程的可见性?

问题三: 既然 volatile 能够保证线程间的变量可见性, 是不是就意味着基于volatile 变量的运算就是并发安全的?

问题四: 请对比下 volatile 对比 Synchronized 的异同。

问题五: 请谈谈 Thread Local 是怎么解决并发安全的?

问题六: 很多人都说要慎用 Thread Local, 谈谈你的理解, 使用Thread Local 需要注意些什么?

Java并发编程实战(中文版)

技术图片
技术图片

清晰的知识图谱(原图)、完整知识点剖析(PDF文档)、高频面试题+答案(PDF文档)、Java并发编程实战(PDF文档),这些资料都免费送哦,正在为面试准备或准备重温知识提升自我的小伙伴即可免费获取全套并发编程学习资料哦!!还有更多资料等着你!!

技术图片
技术图片

以上是关于2020之最专业的并发编程篇:知识图鉴+知识点剖析+高频面试+书籍的主要内容,如果未能解决你的问题,请参考以下文章

并发编程知识点剖析

Java 面试知识点解析——高并发编程篇

高级程序员需知的并发编程知识

陪娃学Arduino之最基础的Arduino 知识你知道吗

陪娃学Arduino之最基础的Arduino 知识你知道吗

2020第一篇环境问题基础知识