迎战金三银四:Java面试避坑之并发编程篇(我踩过的坑不要再进

本文详细解析了Java中的Synchronized和ReentrantLock锁机制,包括它们的工作原理、可重入性、JVM优化策略以及与AQS框架的关系。此外,还探讨了线程池和并发工具如Docker、Kubernetes在实际应用中的重要性。
摘要由CSDN通过智能技术生成

在虚拟机执行到 monitorenter 指令时 , 首先要尝试获取对象的锁 :

如果这个对象没有锁定 , 或者当前线程已经拥有了这个对象的锁 , 把锁的计数器 +1; 当执行 monitorexit 指令时将锁计数器 -1; 当计数器为 0 时 , 锁就被释放了 。

如果获取对象失败了 , 那当前线程就要阻塞等待 , 直到对象锁被另外一个线程释放为止 。

JavaSynchronize 通过在对象头设置标记 , 达到了获取锁和释放锁的目的 。

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

“ 锁 ” 的 本 质 其 实 是 monitorentermonitorexit 字节码指令 的一个 Reference 类型 的 参 数 , 即 要 锁 定 和 解 锁 的 对 象 。 我 们 知 道 , 使 用 Synchronized 可 以 修 饰 不 同 的 对 象 , 因 此 , 对 应 的 对 象 锁 可 以 这 么 确定 。

  1. 如 果 Synchronized 明 确 指 定 了 锁 对 象 , 比 如 Synchronized(变量名)Synchronized(this) 等 , 说 明 加 解 锁 对 象 为 该 对 象 。
  2. 如 果 没 有 明 确 指 定 :
    Synchronized 修饰的方法为 非静态方法 , 表示 此方法对应的对象锁对象
    Synchronized 修饰的方法为 静态方法 , 则表示 此方法对应的类对象锁对象

注 意 , 当 一 个 对 象 被 锁 住 时 , 对 象 里 面 所 有 用 Synchronized 修饰 的方法 都 将 产 生 堵 塞 , 而 对 象 里 非 Synchronized 修 饰 的 方 法 可 正 常 被 调用 , 不 受 锁 影 响 。

问题三 : 什么是可重入性 , 为什么说 Synchronized 是可重入锁?

可重入性 是 锁 的 一 个 基 本 要 求 , 是 为 了 解 决 自 己 锁 死 自 己 的 情 况 。

比 如 , 一 个 类 中 的 同 步 方 法 调 用 另 一 个 同 步 方 法 , 假如 Synchronized 不 支 持 重入 , 进 入 method2 方 法 时 当 前 线 程 获 得 锁 ,method2 方 法 里 面 执 行 method1 时 当 前 线 程 又 要 去 尝 试 获 取 锁 , 这时 如果 不支持 重入 , 它 就 要 等 释 放 , 把 自 己 阻 塞 , 导 致 自 己 锁 死 自 己 。

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 是非公平锁?

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

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

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

其 实 , 锁 的 实 现 原 理 基 本 是 为 了 达 到 一 个 目 的 :

让 所 有 的 线 程 都 能 看 到 某 种 标 记 。

Synchronized 通 过 在 对 象 头 中 设 置 标 记 实 现 了 这 一 目 的 , 是 一 种 JVM 原 生 的 锁 实 现 方 式 , 而 ReentrantLock 以 及 所 有 的 基 于 Lock 接 口 的 实 现 类 , 都 是 通 过 用 一 个 volitile 修 饰 的 int 型 变 量 , 并 保 证 每 个 线 程 都 能 拥 有 对 该 int可见性原子 修 改 , 其 本 质 是 基 于 所 谓 的 AQS 框 架

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

AQSAbstractQueuedSynchronizer 类 ) 是 一 个 用 来 构 建 锁 和 同 步 器 的 框 架 , 各 种 Lock 包 中 的 锁 ( 常 用 的 有 ReentrantLockReadWriteLock) , 以 及 其 他 如 SemaphoreCountDownLatch, 甚至 是 早 期 的 FutureTask 等 , 都 是 基 于 AQS 来 构建。

  1. AQS 在 内 部 定 义 了 一 个 volatile int state 变 量 , 表 示 同 步 状 态 : 当 线 程 调 用 lock 方 法 时 , 如 果 state=0, 说 明 没 有 任 何 线 程 占 有 共 享 资 源 的 锁 , 可 以 获 得 锁 并 将 state=1; 如 果 state=1, 则 说 明 有 线 程 目 前 正 在 使 用 共 享 变 量 , 其 他 线 程 必 须 加 入 同步队列 进 行 等 待 。
  2. AQS 通 过 Node 内部类 构 成 的 一 个 双 向 链 表 结 构 的 同 步 队 列 , 来 完 成 线程 获 取 锁 的 排 队 工 作 , 当 有 线 程 获 取 锁 失 败 后 , 就 被 添 加 到 队 列 末 尾 。
  • Node 类 是 对 要 访 问 同 步 代 码 的 线 程 的 封 装 , 包 含 了 线 程 本 身 及 其 状 态 叫 waitStatus( 有 五 种 不 同 取 值 , 分 别 表 示 是 否 被 阻 塞 , 是 否 等 待 唤 醒 ,是 否 已 经 被 取 消 等 ) , 每 个 Node 结 点 关 联 其 prev 结点next 结点 , 方 便 线 程 释 放 锁 后 快 速 唤 醒 下 一 个 在 等 待 的 线 程 , 是 一 个 FIFO 的 过程 。
  • Node 类 有 两 个 常 量 , SHAREDEXCLUSIVE, 分 别 代 表 共享模式独占模式 。 所 谓 共 享 模 式 是 一 个 锁 允 许 多 条 线 程 同 时 操 作 ( 信 号 量 Semaphore 就 是 基 于 AQS 的 共 享 模 式 实 现 的 ) , 独 占 模 式 是 同 一 个 时 间 段 只 能 有 一 个 线 程 对 共 享 资 源 进 行 操 作 , 多 余 的 请 求 线 程 需 要 排 队 等 待( 如 ReentranLock) 。
  1. AQS 通 过 内部类 ConditionObject 构 建 等 待 队 列 ( 可 有 多 个 ) , 当 Condition 调 用 wait() 方法 后 , 线 程 将 会 加 入 等 待 队 列 中 , 而 当 Condition 调 用 signal() 方法 后 , 线 程 将 从 等 待 队 列 转 移 动 同 步 队 列 中 进 行 锁 竞 争 。
  2. AQSCondition 各 自 维 护 了 不 同 的 队 列 , 在 使 用 LockCondition 的 时 候 , 其 实 就 是 两 个 队 列 的 互 相 移 动 。

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

ReentrantLockLock实现类 , 是 一 个 互 斥 的 同 步 锁 。从 功 能 角 度 , ReentrantLockSynchronized 的 同 步 操 作 更 精 细( 因 为 可 以 像 普 通 对 象 一 样 使 用 ) , 甚 至 实 现 Synchronized 没 有 的 高 级 功 能 , 如 :

  • 等待可中断: 当 持 有 锁 的 线 程 长 期 不 释 放 锁 的 时 候 , 正 在 等 待 的 线 程 可 以 选 择 放 弃 等 待 , 对 处 理 执 行 时 间 非 常 长 的 同 步 块 很 有 用 。
  • 带超时的获取锁尝试: 在 指 定 的 时 间 范 围 内 获 取 锁 , 如 果 时 间 到 了 仍 然 无 法 获 取 则 返 回 。
  • 可以判断是否有线程在排队等待获取锁 。
  • 可以响应中断请求:与 Synchronized 不 同 , 当 获 取 到 锁 的 线 程 被 中 断 时 , 能 够 响 应 中 断 , 中 断 异 常 将 会 被 抛 出 , 同 时 锁 会 被 释 放 。
  • 可以实现公平锁 。

从 锁 释 放 角 度 , SynchronizedJVM 层 面 上 实 现 的 , 不 但 可 以 通 过 一 些 监 控 工 具 监 控 Synchronized 的 锁 定 , 而 且 在 代 码 执 行 出 现 异 常 时 , JVM 会 自 动 释 放 锁 定 ; 但 是 使 用 Lock 则 不 行 , Lock 是 通 过 代 码 实 现 的 , 要 保 证 锁 定 一 定 会 被 释 放 , 就 必 须 将 unLock() 放 到 finally{} 中 。

从 性 能 角 度 , Synchronized 早 期 实 现 比 较 低 效 , 对 比 ReentrantLock, 大 多 数 场 景 性 能 都 相 差 较 大 。但 是 在 Java 6 中 对 其 进 行 了 非 常 多 的 改 进 , 在 竞 争 不 激 烈 时 ,Synchronized 的 性 能 要 优 于 ReetrantLock; 在 高 竞 争 情 况 下 ,Synchronized 的 性 能 会 下 降 几 十 倍 , 但 是 ReetrantLock 的 性 能 能 维 持 常 态 。

ReentrantLock 是如何实现可重入性的?

ReentrantLock 内 部 自 定 义 了 同步器 SyncSync 既 实 现 了 AQS,又 实 现 了 AOS, 而 AOS 提 供 了 一 种 互 斥 锁 持 有 的 方 式 ) , 其 实 就 是 加 锁 的 时 候 通 过 CAS 算法 , 将 线 程 对 象 放 到 一 个 双 向 链 表 中 , 每 次 获 取 锁 的 时 候 , 看 下 当 前 维 护 的 那 个 线 程 ID 和 当 前 请 求 的 线 程 ID 是 否 一 样 , 一 样 就 可 重 入 了 。

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

通 常 所 说 的 并发包( JUC) 也 就 是 java.util.concurrent 及 其 子 包 , 集 中 了 Java 并 发 的 各 种 基 础 工 具 类 , 具 体 主 要 包 括 几 个 方 面 :

  • 提 供 了 CountDownLatchCyclicBarrierSemaphore 等 , 比 Synchronized 更 加 高 级 , 可 以 实 现 更 加 丰 富 多 线 程 操 作 的 同 步 结 构 。
  • 提 供 了 ConcurrentHashMap、 有 序 的 ConcunrrentSkipListMap, 或 者 通 过 类 似 快 照 机 制 实 现 线 程 安 全 的 动 态 数 组 CopyOnWriteArrayList 等 , 各 种 线 程 安 全 的 容 器 。
  • 提 供 了 ArrayBlockingQueueSynchorousQueue 或 针 对 特 定 场 景 的 PriorityBlockingQueue 等 , 各 种 并 发 队 列 实 现 。
  • 强 大 的 Executor 框架 , 可 以 创 建 各 种 不 同 类 型 的 线 程 池 , 调 度 任 务 运 行 等 。

Java 线程池相关问题

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

  • Java 中 , 所 谓 的 线 程 池 中 的 “ 线 程 ” , 其 实 是 被 抽 象 为 了 一 个 静 态 内 部 类 Worker, 它 基 于 AQS 实 现 , 存 放 在 线 程 池 的 HashSet<Worker> workers 成 员 变 量 中 ;
  • 而 需 要 执 行 的 任 务 则 存 放 在 成 员 变 量 workQueueBlockingQueue<Runnable> workQueue) 中 。这 样 , 整 个 线 程 池 实 现 的 基 本 思 想 就 是 : 从 workQueue 中 不 断 取 出需 要 执 行 的 任 务 , 放 在 Workers 中 进 行 处 理 。

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

Java 中 的 线 程 池 的 创 建 其 实 非 常 灵 活 , 我 们 可 以 通 过 配 置 不 同 的 参 数 , 创 建 出 行 为 不 同 的 线 程 池 , 这 几 个 参 数 包 括 :

  • corePoolSize: 线 程 池 的 核 心 线 程 数 。
  • maximumPoolSize: 线 程 池 允 许 的 最 大 线 程 数 。
  • keepAliveTime: 超 过 核 心 线 程 数 时 闲 置 线 程 的 存 活 时 间 。
  • workQueue: 任 务 执 行 前 保 存 任 务 的 队 列 , 保 存 由 execute 方 法 提 交 的 Runnable 任 务 。

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

显 然 不 是 的 。 线 程 池 默 认 初 始 化 后 不 启 动 Worker, 等 待 有 请 求 时 才 启 动 。

每 当 我 们 调 用 execute() 方法 添 加 一 个 任 务 时 , 线 程 池 会 做 如 下 判 断 :

  • 如 果 正 在 运 行 的 线 程 数 量 小 于 corePoolSize, 那 么 马 上 创 建 线 程 运 行 这 个 任 务 ;
  • 如 果 正 在 运 行 的 线 程 数 量 大 于 或 等 于 corePoolSize, 那 么 将 这 个 任 务 放 入 队 列 ;
  • 如 果 这 时 候 队 列 满 了 , 而 且 正 在 运 行 的 线 程 数 量 小 于 maximumPoolSize, 那 么 还 是 要 创 建 非 核 心 线 程 立 刻 运 行 这 个 任 务 ;
  • 如 果 队 列 满 了 , 而 且 正 在 运 行 的 线 程 数 量 大 于 或 等 于 maximumPoolSize, 那 么 线 程 池 会 抛 出 异 常 RejectExecutionException

当 一 个 线 程 完 成 任 务 时 , 它 会 从 队 列 中 取 下 一 个 任 务 来 执 行 。 当 一 个 线 程 无 事 可 做 , 超 过 一 定 的 时 间 ( keepAliveTime) 时 , 线 程 池 会 判 断 。

小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Java工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Java开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频

如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
img

最后

权威指南-第一本Docker书

引领完成Docker的安装、部署、管理和扩展,让其经历从测试到生产的整个开发生命周期,深入了解Docker适用于什么场景。并且这本Docker的学习权威指南介绍了其组件的基础知识,然后用Docker构建容器和服务来完成各种任务:利用Docker为新项目建立测试环境,演示如何使用持续集成的工作流集成Docker,如何构建应用程序服务和平台,如何使用Docker的API,如何扩展Docker。

总共包含了:简介、安装Docker、Docker入门、使用Docker镜像和仓库、在测试中使用Docker、使用Docker构建服务、使用Fig编配Docke、使用Docker API、获得帮助和对Docker进行改进等9个章节的知识。

image

image

image

image

关于阿里内部都在强烈推荐使用的“K8S+Docker学习指南”—《深入浅出Kubernetes:理论+实战》、《权威指南-第一本Docker书》,看完之后两个字形容,爱了爱了!

-1710434333392)]

[外链图片转存中…(img-xGNuQGbC-1710434333392)]

关于阿里内部都在强烈推荐使用的“K8S+Docker学习指南”—《深入浅出Kubernetes:理论+实战》、《权威指南-第一本Docker书》,看完之后两个字形容,爱了爱了!

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

  • 17
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值