疯狂架构师讲解最强:并发编程模式PDF篇,含并发编程面试及答案
2022-03-31 21:30·90后小伙的追梦之路
同步模式之保护性暂停
1.定义
- 即Guarded Suspension,用在一个线程等待另一个线程的执行结果要点
有一个结果需要从一个线程传递到另一个线程,让他们关联同一个GuardedObject - 如果有结果不断从一个线程到另一个线程那么可以使用消息队列(见生产者/消费者)
- JDK中,join的实现、Future的实现,采用的就是此模式
- 因为要等待另一方的结果,因此归类到同步模式
2.实现
应用
一个线程等待另一个线程的执行结果
执行结果
3.带超时版GuardedObject
如果要控制超时时间呢
测试,没有超时
输出
测试,超时
输出
原理之join
4.多任务版GuardedObject
图中Futures就好比居民楼一层的信箱(每个信箱有房间编号),左侧的t0,t2,t4就好比等待邮件的居民,右侧的t1,t3,t5就好比邮递员
如果需要在多个类之间使用GuardedObject对象,作为参数传递不是很方便,因此设计一个用来解耦的中间类,这样不仅能够解耦【结果等待者】和【结果生产者】,还能够同时支持多个任务的管理
新增id用来标识Guarded Object
中间解耦类
业务相关类
测试
某次运行结果
同步模式之Balking
1.定义
Balking(犹豫)模式用在一个线程发现另一个线程或本线程已经做了某一件相同的事,那么本线程就无需再做了,直接结束返回
2.实现
例如:
当前端页面多次点击按钮调用start时
输出
它还经常用来实现线程安全的单例
对比一下保护性暂停模式:保护性暂停模式用在一个线程等待另一个线程的执行结果,当条件不满足时线程等待。
同步模式之顺序控制
1.固定运行顺序
比如,必须先2后1打印
1.1 wait notify版
1.2 Park Unpark版
可以看到,实现上很麻烦:
首先,需要保证先wait再notify,否则wait线程永远得不到唤醒。因此使用了『运行标记』来判断该不该wait
第二,如果有些干扰线程错误地notify了wait线程,条件不满足时还要重新等待,使用了while循环来解决此问题
最后,唤醒对象上的wait线程需要使用notifyAll,因为『同步对象』上的等待线程可能不止一个可以使用LockSupport类的park和unpark来简化上面的题目:
park和unpark方法比较灵活,他俩谁先调用,谁后调用无所谓。并且是以线程为单位进行『暂停』和『恢复』,不需要『同步对象』和『运行标记』
2.交替输出
线程1输出a 5次,线程2输出b 5次,线程3输出c 5次。现在要求输出abcabcabcabcabc怎么实现
2.1 wait notify版
2.2 Lock条件变量版
2.3 Park Unpark版
异步模式之生产者/消费者
1.定义
要点
- 与前面的保护性暂停中的GuardObject不同,不需要产生结果和消费结果的线程一一对应
- 消费队列可以用来平衡生产和消费的线程资源
- 生产者仅负责产生结果数据,不关心数据该如何处理,而消费者专心处理结果数据
- 消息队列是有容量限制的,满时不会再加入数据,空时不会再消耗数据
- JDK中各种阻塞队列,采用的就是这种模式
2.实现
应用
某次运行结果
结果解读
异步模式之工作线程
1.定义
让有限的工作线程(Worker Thread)来轮流异步处理无限多的任务。也可以将其归类为分工模式,它的典型实现就是线程池,也体现了经典设计模式中的享元模式。
例如,海底捞的服务员(线程),轮流处理每位客人的点餐(任务),如果为每位客人都配一名专属的服务员,那么成本就太高了(对比另一种多线程设计模式:Thread-Per-Message)
注意,不同任务类型应该使用不同的线程池,这样能够避免饥饿,并能提升效率
例如,如果一个餐馆的工人既要招呼客人(任务类型A),又要到后厨做菜(任务类型B)显然效率不咋地,分成服务员(线程池A)与厨师(线程池B)更为合理,当然你能想到更细致的分工
2.饥饿
- 固定大小线程池会有饥饿现象
两个工人是同一个线程池中的两个线程 - 他们要做的事情是:为客人点餐和到后厨做菜,这是两个阶段的工作
- 客人点餐:必须先点完餐,等菜做好,上菜,在此期间处理点餐的工人必须等待
- 后厨做菜:没啥说的,做就是了
- 比如工人A处理了点餐任务,接下来它要等着工人B把菜做好,然后上菜,他俩也配合的蛮好
- 但现在同时来了两个客人,这个时候工人A和工人B都去处理点餐了,这时没人做饭了,饥饿
输出
当注释取消后,可能的输出
解决方法可以增加线程池的大小,不过不是根本解决方案,还是前面提到的,不同的任务类型,采用不同的线程池,例如:
输出
创建多少线程池合适
过小会导致程序不能充分地利用系统资源、容易导致饥饿
过大会导致更多的线程上下文切换,占用更多内存
3.1 CPU密集型运算
通常采用cpu核数+ 1能够实现最优的CPU利用率,+1是保证当线程由于页缺失故障(操作系统)或其它原因导致暂停时,额外的这个线程就能顶上去,保证CPU时钟周期不被浪费
3.2 I/O密集型运算
CPU不总是处于繁忙状态,例如,当你执行业务计算时,这时候会使用CPU资源,但当你执行I/O操作时、远程RPC调用时,包括进行数据库操作时,这时候CPU就闲下来了,你可以利用多线程提高它的利用率。经验公式如下
线程数=核数*期望CPU利用率*总时间(CPU计算时间+等待时间) / CPU计算时间
例如4核CPU计算时间是50%,其它等待时间是50%,期望cpu被100%利用,套用公式
4 * 100% * 100% / 50% = 8
例如4核CPU计算时间是10%,其它等待时间是90%,期望cpu被100%利用,套用公式
4 * 100% * 100% / 10% = 40
自定义线程池
步骤1:自定义拒绝策略接口
步骤2:自定义任务队列
步骤3:自定义线程池
步骤4:测试
终止模式之两阶段终止模式
Two Phase Termination
在一个线程T1中如何“优雅”终止线程T2?这里的【优雅】指的是给T2一个料理后事的机会。
1.错误思路
使用线程对象的stop()方法停止线程
stop方法会真正杀死线程,如果这时线程锁住了共享资源,那么当它被杀死后就再也没有机会释放锁,其它线程将永远无法获取锁
使用System.exit(int)方法停止线程
目的仅是停止一个线程,但这种做法会让整个程序都停止
2.两阶段终止模式
2.1利用isInterrupted
interrupt可以打断正在执行的线程,无论这个线程是在sleep,wait,还是正常运行
调用
结果
2.2利用停止标记
调用
结果
案例:JVM内存监控
线程安全单例
单例模式有很多实现方法,饿汉、懒汉、静态内部类、枚举类,试分析每种实现下获取单例对象(即调用getInstance)时的线程安全,并思考注释中的问题
1.饿汉单例
2.枚举单例
3.懒汉单例
4. DCL懒汉单例
5.静态内部类懒汉单例
享元模式
1.简介
定义英文名称:Flyweight pattern.当需要重用数量有限的同一类对象时
出自"Gang of Four" design patterns
归类Structual patterns
2.体现
2.1包装类
在JDK中Boolean,Byte,Short,Integer,Long,Character等包装类提供了valueOf方法,例如Long的valueOf会缓存-128~127之间的Long对象,在这个范围之间会重用对象,大于这个范围,才会新建Long对象:
2.2 String串池
2.3 BigDecimal BigInteger
3. DIY
例如:一个线上商城应用,QPS达到数千,如果每次都重新创建和关闭数据库连接,性能会受到极大影响。这时预先创建好一批连接,放入连接池。一次请求到达后,从连接池获取连接,使用完毕后再还回连接池,这样既节约了连接的创建和关闭时间,也实现了连接的重用,不至于让庞大的连接数压垮数据库。
使用连接池:
以上实现没有考虑:
连接的动态增长与收缩
连接保活(可用性检测)
等待超时处理
分布式hash
对于关系型数据库,有比较成熟的连接池实现,例如c3p0, druid等对于更通用的对象池,可以考虑使用apachecommons pool,例如redis连接池可以参考jedis中关于连接池的实现
并发编程面试及答案
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不 支 持 重 入 ,进 入method2方 法 时 当 前 线 程 获 得 锁 ,method2方 法 里 面 执 行method1时 当 前 线 程 又 要 去 尝 试 获 取 锁 ,这时 如 果 不 支 持 重 入 ,它 就 要 等 释 放 ,把 自 己 阻 塞 ,导 致 自 己 锁 死 自 己 。
对Synchronized来 说 ,可 重 入 性 是 显 而 易 见 的 ,刚 才 提 到 ,在 执 行monitorenter指 令 时 ,如 果 这 个 对 象 没 有 锁 定 ,或 者 当 前 线 程 已 经 拥有 了 这 个 对 象 的 锁(而 不 是 已 拥 有 了 锁 则 不 能 继 续 获 取 ),就 把 锁 的 计数 器+1,其 实 本 质 上 就 通 过 这 种 方 式 实 现 了 可 重 入 性 。
问 题 四:JVM对Java的 原 生 锁 做 了 哪 些 优 化 ?
在Java6之 前 ,Monitor的 实 现 完 全 依 赖 底 层 操 作 系 统 的 互 斥 锁 来实 现 ,也 就 是 我 们 刚 才 在 问 题 二 中 所 阐 述 的 获 取/释 放 锁 的 逻 辑 。由 于Java层 面 的 线 程 与 操 作 系 统 的 原 生 线 程 有 映 射 关 系 ,如 果 要 将 一个 线 程 进 行 阻 塞 或 唤 起 都 需 要 操 作 系 统 的 协 助 ,这 就 需 要 从 用 户 态 切 换到 内 核 态 来 执 行 ,这 种 切 换 代 价 十 分 昂 贵 ,很 耗 处 理 器 时 间 ,现 代JDK中 做 了 大 量 的 优 化 。
一 种 优 化 是 使 用 自 旋 锁 ,即 在 把 线 程 进 行 阻 塞 操 作 之 前 先 让 线 程 自 旋 等待 一 段 时 间 ,可 能 在 等 待 期 间 其 他 线 程 已 经 解 锁 ,这 时 就 无 需 再 让 线 程执 行 阻 塞 操 作 ,避 免 了 用 户 态 到 内 核 态 的 切 换 。现 代JDK中 还 提 供 了 三 种 不 同 的Monitor实 现 ,也 就 是 三 种 不 同 的锁:
1.偏 向 锁 (BiasedLocking)
2.轻 量 级 锁
3.重 量 级 锁
这 三 种 锁 使 得JDK得 以 优 化Synchronized的 运 行 ,当JVM检 测到 不 同 的 竞 争 状 况 时 ,会 自 动 切 换 到 适 合 的 锁 实 现 ,这 就 是 锁 的 升 级 、降 级 。
当 没 有 竞 争 出 现 时 ,默 认 会 使 用 偏 向 锁 。
JVM会 利 用CAS操 作 ,在 对 象 头 上 的MarkWord部 分 设 置 线 程ID,以 表 示 这 个 对 象 偏 向 于 当 前 线 程 ,所 以 并 不 涉 及 真 正 的 互 斥 锁 ,因为 在 很 多 应 用 场 景 中 ,大 部 分 对 象 生 命 周 期 中 最 多 会 被 一 个 线 程 锁 定 ,使 用 偏 斜 锁 可 以 降 低 无 竞 争 开 销 。
如 果 有 另 一 线 程 试 图 锁 定 某 个 被 偏 斜 过 的 对 象 ,JVM就 撤 销 偏 斜 锁 ,切 换 到 轻 量 级 锁 实 现 。
轻 量 级 锁 依 赖CAS操 作MarkWord来 试 图 获 取 锁 ,如 果 重 试 成 功 ,就 使 用 普 通 的 轻 量 级 锁 ;否 则 ,进 一 步 升 级 为 重 量 级 锁 。
问 题 五:为 什 么 说Synchronized是 非 公 平 锁 ?
非 公 平 主 要 表 现 在 获 取 锁 的 行 为 上 ,并 非 是 按 照 申 请 锁 的 时 间 前 后 给 等待 线 程 分 配 锁 的 ,每 当 锁 被 释 放 后 ,任 何 一 个 线 程 都 有 机 会 竞 争 到 锁 ,这 样 做 的 目 的 是 为 了 提 高 执 行 性 能 ,缺 点 是 可 能 会 产 生 线 程 饥 饿 现 象 。
问 题 六:什 么 是 锁 消 除 和 锁 粗 化 ?
问 题 七:为 什 么 说Synchronized是 一 个 悲 观 锁 ?乐 观 锁 的 实 现 原 理又 是 什 么 ?什 么 是CAS,它 有 什 么 特 性 ?
问 题 八:乐 观 锁 一 定 就 是 好 的 吗 ?
可 重 入 锁ReentrantLock及 其 他 显 式 锁 相 关 问 题
问 题 一:跟Synchronized相 比 ,可 重 入 锁ReentrantLock其 实 现原 理 有 什 么 不 同 ?
问 题 二:那 么 请 谈 谈AQS框 架 是 怎 么 回 事 儿 ?
问 题 三:请 尽 可 能 详 尽 地 对 比 下Synchronized和ReentrantLock的 异 同 。
问 题 四:ReentrantLock是 如 何 实 现 可 重 入 性 的 ?
问 题 五:除 了ReetrantLock,你 还 接 触 过JUC中 的 哪 些 并 发 工 具 ?
问 题 六:请 谈 谈ReadWriteLock和StampedLock。
问 题 七:如 何 让Java的 线 程 彼 此 同 步 ?你 了 解 过 哪 些 同 步 器 ?请 分 别介 绍 下 。
问 题 八:CyclicBarrier和CountDownLatch看 起 来 很 相 似 ,请 对 比下 呢 ?
Java线 程 池 相 关 问 题问
题 一:Java中 的 线 程 池 是 如 何 实 现 的 ?
问 题 二:创 建 线 程 池 的 几 个 核 心 构 造 参 数 ?
问 题 三:线 程 池 中 的 线 程 是 怎 么 创 建 的 ?是 一 开 始 就 随 着 线 程 池 的 启 动创 建 好 的 吗 ?
问 题 四:既 然 提 到 可 以 通 过 配 置 不 同 参 数 创 建 出 不 同 的 线 程 池 ,那 么Java中 默 认 实 现 好 的 线 程 池 又 有 哪 些 呢 ?请 比 较 它 们 的 异 同 。
问 题 五:如 何 在Java线 程 池 中 提 交 线 程 ?
Java内 存 模 型 相 关 问 题
问 题 一:什 么 是Java的 内 存 模 型 ,Java中 各 个 线 程 是 怎 么 彼 此 看 到对 方 的 变 量 的 ?
问 题 二:请 谈 谈volatile有 什 么 特 点 ,为 什 么 它 能 保 证 变 量 对 所 有 线程 的 可 见 性 ?
问 题 三:既 然volatile能 够 保 证 线 程 间 的 变 量 可 见 性 ,是 不 是 就 意 味着 基 于volatile变 量 的 运 算 就 是 并 发 安 全 的 ?
问 题 四:请 对 比 下volatile对 比Synchronized的 异 同 。
问 题 五:请 谈 谈ThreadLocal是 怎 么 解 决 并 发 安 全 的 ?
问 题 六:很 多 人 都 说 要 慎 用ThreadLocal,谈 谈 你 的 理 解 ,使 用ThreadLocal需 要 注 意 些 什 么 ?
1)现在有T1、T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行?
2)在Java中Lock接口比synchronized块的优势是什么?你需要实现一个高效的缓存,它允许多个用户读,但只允许一个用户写,以此来保持它的完整性,你会怎样去实现它?
3)在java中wait和sleep方法的不同?
4)用Java实现阻塞队列。
5)用Java写代码来解决生产者——消费者问题。
6)用Java编程一个会导致死锁的程序,你将怎么解决?
7)什么是原子操作,Java中的原子操作是什么?
8)Java中的volatile关键是什么作用?怎样使用它?在Java中它跟synchronized方法有什么不同?
9)什么是竞争条件?你怎样发现和解决竞争?
10)你将如何使用threaddump?你将如何分析Threaddump?
11)为什么我们调用start()方法时会执行run()方法,为什么我们不能直接调用run()方法?
12)Java中你怎样唤醒一个阻塞的线程?
13)在Java中CycliBarriar和CountdownLatch有什么区别?
14)什么是不可变对象,它对写并发应用有什么帮助?
15)你在多线程环境中遇到的常见的问题是什么?你是怎么解决它的?
总结
由于笔记资料太多了,我罗列了一小部分,像并发编程面试题及答案前几题我已经解析出来了,后面的由于平台限制我就不写出来了,需要完整面试题答案笔记资料的朋友可以直接来找我免费领取这篇PDF笔记资料,领取方式:点赞+转发+关注!后台小信封回复【444】即可领取。