文章目录
- 查看锁的信息
- 饥饿死锁的例子
- Synchronized 其 原 理 是 什 么 ?
- 你 刚 才 提 到 获 取 对 象 的 锁 ,这 个“ 锁 ”到 底 是 什 么 ? 如 何 确 定 对象的锁
- 什 么 是 可 重 入 性 , 为 什 么 说 Synchronized 是 可 重 入 锁 ?
- 为 什 么 说 Synchronized 是 一 个 悲 观 锁 ? 乐 观 锁 的 实 现 原 理 又 是 什 么 ? 什 么 是 CAS, 它 有 什 么 特 性 ?
- 乐 观 锁 一 定 就 是 好 的 吗 ?
- 跟 Synchronized 相 比 , 可 重 入 锁 ReentrantLock 其实现原理 有 什 么 不 同 ?
- AQS框 架 是 怎 么 回 事 儿 ?
- synchronized实例
查看锁的信息
jvisualvm
查看pid
死锁代码:
C:\hello>jstack -l 20488
饥饿死锁的例子
Synchronized 其 原 理 是 什 么 ?
Synchronized 是 由 JVM 实 现 的 一 种 实 现 互 斥 同 步 的 一 种 方 式 , 如 果你查看被 Synchronized 修 饰 过 的 程 序 块 编 译 后 的 字 节 码 , 会 发 现 , 被Synchronized 修 饰 过 的 程 序 块 , 在 编 译 前 后 被 编 译 器 生 成了 monitorenter 和 monitorexit 两 个 字 节 码 指 令 。
这 两 个 指 令 是 什 么 意 思 呢 ?
在 虚 拟 机 执 行 到 monitorenter 指 令 时 , 首 先 要 尝 试 获 取 对 象 的 锁 :
如 果 这 个 对 象 没 有 锁 定 ,或 者 当 前 线 程 已 经 拥 有 了 这 个 对 象 的 锁 ,把 锁 的计数器 +1;当 执 行 monitorexit 指 令 时 将 锁 计 数 器 -1;当 计 数 器 为 0 时 , 锁 就 被 释 放 了 。
如 果 获 取 对 象 失 败 了 ,那 当 前 线 程 就 要 阻 塞 等 待 ,直 到 对 象 锁 被 另 外 一 个线 程 释 放 为 止 。
Java 中 Synchronize 通 过 在 对 象 头 设 置 标 记 , 达 到 了 获 取 锁 和 释 放 锁的目的。
你 刚 才 提 到 获 取 对 象 的 锁 ,这 个“ 锁 ”到 底 是 什 么 ? 如 何 确 定 对象的锁
“ 锁 ” 的 本 质 其 实 是 monitorenter 和 monitorexit 字 节 码 指 令 的 一 个
Reference 类 型 的 参 数 , 即 要 锁 定 和 解 锁 的 对 象 。 我 们 知 道 , 使 用Synchronized 可 以 修 饰 不 同 的 对 象 ,因 此 ,对 应 的 对 象 锁 可 以 这 么 确 定 。
- 如 果 Synchronized 明 确 指 定 了 锁 对 象 ,比 如 Synchronized( 变 量 名 )、Synchronized(this) 等 , 说 明 加 解 锁 对 象 为 该 对 象 。
- 如 果 没 有 明 确 指 定 :
若 Synchronized 修 饰 的 方 法 为 非 静 态 方 法 ,表 示 此 方 法 对 应 的 对 象 为 锁对象;
若 Synchronized 修 饰 的 方 法 为 静 态 方 法 ,则 表 示 此 方 法 对 应 的 类 对 象 为锁对象。
注 意 , 当 一 个 对 象 被 锁 住 时 , 对象里面有用Synchronized 修饰的方法 都 将 产 生 堵 塞 , 而 对 象 里 非 Synchronized 修 饰 的 方 法 可 正 常 被 调 用 ,不 受 锁 影 响 。
什 么 是 可 重 入 性 , 为 什 么 说 Synchronized 是 可 重 入 锁 ?
可 重 入 性 是 锁 的 一 个 基 本 要 求 , 是 为 了 解 决 自 己 锁 死 自 己 的 情 况 。
比 如一 个 类 中 的 同 步 方 法 调 用 另 一 个 同 步 方 法 , 假 如
Synchronized 不 支 持 重 入 , 进 入 method2 方 法 时 当 前 线 程 获 得 锁 ,method2 方 法 里 面 执 行 method1 时 当 前 线 程 又 要 去 尝 试 获 取 锁 , 这时 如 果 不 支 持 重 入 , 它 就 要 等 释 放 , 把 自 己 阻 塞 , 导 致 自 己 锁 死 自 己 。
为 什 么 说 Synchronized 是 一 个 悲 观 锁 ? 乐 观 锁 的 实 现 原 理 又 是 什 么 ? 什 么 是 CAS, 它 有 什 么 特 性 ?
Synchronized 显 然 是 一 个 悲 观 锁 , 因 为 它 的 并 发 策 略 是 悲 观 的 :
不 管 是 否 会 产 生 竞 争 ,任 何 的 数 据 操 作 都 必 须 要 加 锁 、用 户 态 核 心 态 转 换 、维 护 锁 计 数 器 和 检 查 是 否 有 被 阻 塞 的 线 程 需 要 被 唤 醒 等 操 作 。
随 着 硬 件 指 令 集 的 发 展 ,我 们 可 以 使 用 基 于 冲 突 检 测 的 乐 观 并 发 策 略 。先进 行 操 作 , 如 果 没 有 其 他 线 程 征 用 数 据 , 那 操 作 就 成 功 了 ;如 果 共 享 数 据 有 征 用 ,产 生 了 冲 突 ,那 就 再 进 行 其 他 的 补 偿 措 施 。这 种 乐观 的 并 发 策 略 的 许 多 实 现 不 需 要 线 程 挂 起 , 所 以 被 称 为 非 阻 塞 同 步 。
乐 观 锁 的 核 心 算 法 是 CAS( Compareand Swap,比较并交换 ) , 它 涉及 到 三 个 操 作 数 :内 存 值 、预 期 值 、新 值 。当 且 仅 当 预 期 值 和 内 存 值 相 等时才将内存值改为新值,这 样 处 理 的 逻 辑 是 , 首 先 检 查 某 块 内 存 的 值 是 否 跟 之 前 我 读 取 时 的 一 样 ,如 不 一 样 则 表 示 期 间 此 内 存 值 已 经 被 别 的 线 程 更 改 过 ,舍 弃 本 次 操 作 ,否
则 说 明 期 间 没 有 其 他 线 程 对 此 内 存 值 操 作 , 可 以 把 新 值 设 置 给 此 块 内 存 。
CAS 具 有 原 子 性 ,它 的 原 子 性 由 CPU 硬 件 指 令 实 现 保 证 ,即 使 用 JNI 调 用 Native 方 法 调 用 由 C++ 编 写 的 硬 件 级 别 指 令 , JDK 中 提 供 了Unsafe 类 执 行 这 些 操 作 。
乐 观 锁 一 定 就 是 好 的 吗 ?
乐 观 锁 避 免 了 悲 观 锁 独 占 对 象 的 现 象 ,同 时 也 提 高 了 并 发 性 能 ,但 它 也 有缺点:
- 乐 观 锁 只 能 保 证 一 个 共 享 变 量 的 原 子 操 作 。如 果 多 一 个 或 几 个 变 量 ,乐 观锁 将 变 得 力 不 从 心 ,但 互 斥 锁 能 轻 易 解 决 ,不 管 对 象 数 量 多 少 及 对 象 颗 粒度大小。
- 长 时 间 自 旋 可 能 导 致 开 销 大 。 假 如 CAS 长 时 间 不 成 功 而 一 直 自 旋 , 会给 CPU 带 来 很 大 的 开 销 。
- ABA 问题。CAS 的 核 心 思 想 是 通 过 比 对 内 存 值 与 预 期 值 是 否 一 样 而 判 断内 存 值 是 否 被 改 过 , 但 这 个 判 断 逻 辑 不 严 谨 , 假 如 内 存 值 原 来 是 A, 后来 被 一 条 线 程 改 为 B, 最 后 又 被 改 成 了 A, 则 CAS 认 为 此 内 存 值 并 没有 发 生 改 变 ,但 实 际 上 是 有 被 其 他 线 程 改 过 的 ,这 种 情 况 对 依 赖 过 程 值 的情 景 的 运 算 结 果 影 响 很 大 。解 决 的 思 路 是 引 入 版 本 号 ,每 次 变 量 更 新 都 把版 本 号 加 一 。
可重入锁 ReentrantLock 及 其他 显 式 锁相 关 问题
跟 Synchronized 相 比 , 可 重 入 锁 ReentrantLock 其实现原理 有 什 么 不 同 ?
其 实 , 锁 的 实 现 原 理 基 本 是 为 了 达 到 一 个 目 的 :
让 所 有 的 线 程 都 能 看 到 某 种 标 记 。
Synchronized 通过在对象头中设置标记实现了这一目的,是一种 JVM
原 生 的 锁 实 现 方 式 , 而 ReentrantLock 以 及 所 有 的 基 于 Lock 接 口 的实 现 类 ,都 是 通 过 用 一 个 volitile 修饰的 int 型 变 量 ,并 保 证 每 个 线 程都 能 拥 有 对 该 int 的 可 见 性 和 原 子 修 改 ,其 本 质 是 基 于 所 谓 的 AQS 框架 。
AQS框 架 是 怎 么 回 事 儿 ?
AQS( AbstractQueuedSynchronizer 类 ) 是 一 个 用 来 构 建 锁 和 同 步 器的 框 架 , 各 种Lock 包 中 的 锁 ( 常 用 的 有ReentrantLock 、ReadWriteLock) , 以 及 其 他 如 Semaphore、 CountDownLatch, 甚至 是 早 期 的 FutureTask 等 , 都 是 基 于 AQS 来构建。
5. AQS 在 内 部 定 义 了 一 个 volatile int state 变 量 , 表 示 同 步 状 态 : 当 线 程调 用 lock 方法时 ,如 果 state=0,说 明 没 有 任 何 线 程 占 有 共 享 资 源 的 锁 ,可 以 获 得 锁 并 将 state=1;如果 state=1, 则 说 明 有 线 程 目 前 正 在 使 用 共享 变 量 , 其 他 线 程 必 须 加 入 同 步 队 列 进 行 等 待 。
6. AQS 通 过 Node 内 部 类 构 成 的 一 个 双 向 链 表 结 构 的 同 步 队 列 , 来 完 成 线程 获 取 锁 的 排 队 工 作 , 当 有 线 程 获 取 锁 失 败 后 , 就 被 添 加 到 队 列 末 尾 。
Node 类 是 对 要 访 问 同 步 代 码 的 线 程 的 封 装 , 包 含 了 线 程 本 身 及 其 状 态 叫waitStatus( 有 五 种 不 同 取 值 , 分 别 表 示 是 否 被 阻 塞 , 是 否 等 待 唤 醒 , 是否 已 经 被 取 消 等 ) , 每 个 Node 结 点 关 联 其 prev 结点和 next 结点,方 便 线 程 释 放 锁 后 快 速 唤 醒 下 一 个 在 等 待 的 线 程 , 是 一 个 FIFO 的过程。
Node 类 有 两 个 常 量 , SHARED 和 EXCLUSIVE, 分 别 代 表 共 享 模 式 和 独占 模 式 。 所 谓 共 享 模 式 是 一 个 锁 允 许 多 条 线 程 同 时 操 作 ( 信 号 量Semaphore 就 是 基 于 AQS 的 共 享 模 式 实 现 的 ) , 独 占 模 式 是 同 一 个 时间 段 只 能 有 一 个 线 程 对 共 享 资 源 进 行 操 作 , 多 余 的 请 求 线 程 需 要 排 队 等 待( 如 ReentranLock) 。
7. AQS 通 过 内 部 类 ConditionObject 构 建 等 待 队 列 ( 可 有 多 个 ) , 当Condition 调 用 wait() 方 法 后 , 线程将会加入等待队列中 , 而当Condition 调 用 signal() 方 法 后 , 线 程 将 从 等 待 队 列 转 移 动 同 步 队 列 中进 行 锁 竞 争 。
synchronized实例
1.synchronized:内置的Java关键字
LOCK:一个接口,下面有多个实现类,可以判断是否取得了锁
2.synchronized自动释放锁,lock必须手动释放锁
3.synchronized不可以中断,非公平锁,lock 可以设置公平还是非公平
前者适合锁少量代码同步问题,后者适合锁大量同步代码
一个用synchronized的例子1:
1this.notify()去掉this对结果无影响
以上例子如果再加一个线程C,改成notifyAll()会出现问题:
A->1
B->0
A->1
B->0
C->1
A->2
C->3
B->2
B->1
B->0
C->1
A->2
发现输出不正确了,是因为/if和while的虚假唤醒问题 ,改if为while
增加CD两个线程:
输出
A->1
B->0
A->1
B->0
A->1
B->0
A->1
B->0
A->1
B->0
C->1
D->0
C->1
D->0
C->1
D->0
C->1
D->0
C->1
D->0
五个为一轮
例子2:
输出卖衣服
卖包子
如果sell2和sell1改一下顺序,则输出变成卖包子 卖衣服
说明:sychronized锁的对象是方法的调用者,由于上面两个方法用的是同一个锁,因此谁先拿到锁先执行谁
加了sleep方法:
卖衣服B
卖包子A
卖包子B
卖衣服A
记住:非同步方法不受锁的影响
如果sychronized修饰的方法是static的,则锁的是整个class,此时即使即使new了不同的两个实例,仍然是同一把锁