
JUC包下并发安全使用的类线程池ThreadPoolExecutorExecutors并发集合类ConcurrentHashMapCopyOnWriteArrayList同步工具类CountDownLatchCyclicBarrierSemaphore原子类AtomicIntegerAtomicReferenceJava中常用的锁和使用场景内置锁synchronizedJava中的synchronized关键字是内置锁机制的基础可以用于方法或代码块。当一个线程进入synchronized代码块或方法时它会获取关联对象的锁当线程离开该代码块或方法时锁会被释放。如果其他线程尝试获取同一个对象的锁它们将被阻塞直到锁被释放。其中syncronized加锁时有无锁、偏向锁、轻量级锁和重量级锁几个级别。偏向锁用于当一个线程进入同步块时如果没有任何其他线程竞争就会使用偏向锁以减少锁的开销。轻量级锁使用线程栈上的数据结构避免了操作系统级别的锁。重量级锁则涉及操作系统级的互斥锁ReentrantLockjava.util.concurrent.locks.ReentrantLock是一个显式的锁类提供了比synchronized更高级的功能如可中断的锁等待、定时锁等待、公平锁选项等。ReentrantLock使用lock()和unlock()方法来获取和释放锁。其中公平锁按照线程请求锁的顺序来分配锁保证了锁分配的公平性但可能增加锁的等待时间。非公平锁不保证锁分配的顺序可以减少锁的竞争提高性能但可能造成某些线程的饥饿读写锁ReadWriteLockjava.util.concurrent.locks.ReadWriteLock接口定义了一种锁允许多个读取者同时访问共享资源但只允许一个写入者。读写锁通常用于读取远多于写入的情况以提高并发性乐观锁和悲观锁悲观锁Pessimistic Locking通常指在访问数据前就锁定资源假设最坏的情况即数据很可能被其他线程修改。synchronized和ReentrantLock都是悲观锁的例子。乐观锁Optimistic Locking通常不锁定资源而是在更新数据时检查数据是否已被其他线程修改。乐观锁常使用版本号或时间戳来实现自旋锁自旋锁是一种锁机制线程在等待锁时会持续循环检查锁是否可用而不是放弃CPU并阻塞。通常可以使用CAS来实现。这在锁等待时间很短的情况下可以提高性能但过度自旋会浪费CPU资源synchronized锁的升级为什么要升级在 JDK1.5 及之前synchronized 完全依赖操作系统内核的互斥量实现是典型的重量级锁每次加锁解锁都需要完成用户态到内核态的切换性能开销极大锁升级的哲学逻辑能不加锁就不加锁无锁 只有一个线程来那就记住你偏向锁 偶尔有第二个线程来你们自旋抢一下就行轻量级锁 很多人来抢老实排队让操作系统管重量级锁Mark WordMark Word存在对象头中对象头是 JVM 存储对象元信息的地方和你写的业务字段无关。主要包含 Mark Word存锁信息、hashcode、GC年龄和 类型指针指向类的元数据┌──────────────────────────────────────────────────────┐ │ │ │ 一个 Java 对象在内存中的布局 │ │ │ ├──────────────────────────────────────────────────────┤ │ │ │ ┌────────────────────────────────────────────┐ │ │ │ 对象头 (Header) │ │ │ │ ┌─────────────────────┬────────────────┐ │ │ │ │ │ Mark Word │ Klass Pointer │ │ │ │ │ │ (64 bit) │ (32/64 bit) │ │ │ │ │ └─────────────────────┴────────────────┘ │ │ │ │ ┌────────────────────────────────────┐ │ │ │ │ │ Array Length (仅数组对象有) │ │ │ │ │ │ (32 bit) │ │ │ │ │ └────────────────────────────────────┘ │ │ │ └────────────────────────────────────────────┘ │ │ │ │ ┌────────────────────────────────────────────┐ │ │ │ 实例数据 (Instance Data) │ │ │ │ 对象的字段值int age, String name ... │ │ │ └────────────────────────────────────────────┘ │ │ │ │ ┌────────────────────────────────────────────┐ │ │ │ 对齐填充 (Padding) │ │ │ │ 使对象总大小为 8 字节的整数倍 │ │ │ └────────────────────────────────────────────┘ │ │ │ └──────────────────────────────────────────────────────┘Mark Word里面存的东西锁信息 当前是偏向锁/轻量级锁/重量级锁以及谁持有锁hashcode 调用 hashCode() 后存入懒加载的GC分代年龄 4 bit所以最大值 15这就是为什么最大分代年龄是 15类型指针相关 指向对象属于哪个类┌─────────────┬──────────────────────────────────────┬────────┬─────────────┐ │ 状态 │ 内容 (64 bit) │ 标志位 │ 是否存锁信息│ ├─────────────┼──────────────────────────────────────┼────────┼─────────────┤ │ │ unused:25 │ │ │ │ 无锁 │ hashcode:31 │ 001 │ 否 │ │ │ age:4 │ │ │ │ │ biased_lock:0 │ │ │ ├─────────────┼──────────────────────────────────────┼────────┼─────────────┤ │ │ thread:54 │ │ │ │ 偏向锁 │ epoch:2 │ 101 │ 是(偏向) │ │ │ age:4 │ │ │ │ │ biased_lock:1 │ │ │ ├─────────────┼──────────────────────────────────────┼────────┼─────────────┤ │ │ ptr_to_lock_record:62 │ 00 │ 是(轻量) │ │ 轻量级锁 │ │ │ │ ├─────────────┼──────────────────────────────────────┼────────┼─────────────┤ │ │ ptr_to_heavyweight_monitor:62 │ 10 │ 是(重量) │ │ 重量级锁 │ │ │ │ ├─────────────┼──────────────────────────────────────┼────────┼─────────────┤ │ │ 空 │ 11 │ 否 │ │ GC标记 │ │ │ │ └─────────────┴──────────────────────────────────────┴────────┴─────────────┘从低级到高级锁无锁0 01所有线程都可以自由访问没有任何锁信息。 对象刚 new 出来就是这个状态。 hashcode、GC分代年龄都存在 Mark Word 里。偏向锁1 01Thread A 第一次进入 synchronized 块 │ ├─ 用 CAS 把自己的线程ID 写入 Mark Word │ └─ 写入成功 → 以后 Thread A 再来直接比较线程ID 匹配就直接进入0 开销连CAS都不用 Thread A 再次进入: 只需要检查 Mark Word 中的 threadID 自己的 ID ├─ 是 → 直接进入无任何同步操作极致优化 └─ 否 → 说明有另一个线程来了触发撤销 Thread B 也来申请这把锁 │ ├─ Thread B 检查发现 threadID ≠ 自己 │ └─ 触发偏向锁撤销 1. 暂停拥有偏向锁的线程 Thread A需要等安全点 safepoint 2. 检查 Thread A 是否还活着 │ ├─ Thread A 已退出同步块 → 锁对象恢复为无锁状态 │ 然后 Thread B 重新尝试获取 │ └─ Thread A 还在同步块中 → 升级为轻量级锁轻量级锁0 0一般锁都是synchronized对象线程要用这个锁的时候会将这个对象的Mark Word复制到这个线程的Lock Record中然后把对象的Mark Word指向线程的Lock Record中不用的时候写回去拿锁的过程Thread A 要执行 synchronized(obj) 第1步在 Thread A 的栈帧中创建一个 Lock Record ┌─────────────────┐ │ Lock Record │ │ [ 空 ] │ ← 还没填东西 └─────────────────┘ 第2步把 obj 的 Mark Word 复制到 Lock Record 里 ┌─────────────────┐ obj 对象头 │ Lock Record │ ┌──────────────┐ │ [ hashcode, │ ←── │ Mark Word │ │ age, 001 ] │ 复制 │ hashcode, │ └─────────────────┘ │ age, 001 │ └──────────────┘ 第3步CAS 修改 obj 的 Mark Word让它指向 Lock Record obj 对象头 ┌──────────────────────┐ │ ptr → Lock Record │00│ ← Mark Word 变成了指针 00标志 └──────────────────────┘ CAS 的含义 比较 obj 的 Mark Word 是否还是第2步复制时的那个值 ├─ 是 → 说明没人抢成功把 Mark Word 改成指针 └─ 不是 → 说明有人抢了CAS 失败 第4步CAS 成功 → Thread A 获得轻量级锁进入同步块释放锁的过程Thread A 退出 synchronized(obj) 第1步把 Lock Record 里备份的 Displaced Mark Word 通过 CAS 写回 obj 的 Mark Word obj 对象头 ┌──────────────────────┐ ┌─────────────────┐ │ hashcode, age, 001 │ ←──CAS──│ Lock Record │ └──────────────────────┘ │ [ hashcode, │ │ age, 001 ] │ └─────────────────┘ 第2步CAS 成功 → 释放完毕对象恢复无锁状态 CAS 失败 → 说明锁已经膨胀成重量级锁了需要唤醒等待的线程竞争过程Thread A 正持有轻量级锁 Thread B 这时也想获取这把锁 第1步Thread B 执行 CAS 尝试修改 Mark Word → 失败已被 A 改了 第2步Thread B 不会立即放弃而是进入 自旋 状态 自旋 一个 while 循环反复尝试 CAS while (true) { if (CAS 成功) { 拿到锁退出循环; break; } // 失败了就继续循环再试 } 第3步自旋结果有两种 ├─ 自旋过程中 Thread A 释放了锁 → Thread B CAS 成功拿到锁 │ 皆大欢喜全程用户态没有系统调用 │ └─ 自旋次数超过阈值还没拿到 → 膨胀为重量级锁下面讲重量级锁0 1Monitor每个 Java 对象都可以关联一个 Monitor 对象重量级锁激活时才创建 ┌─────────────────────────────────────────┐ │ ObjectMonitor │ │ │ │ _owner ──────→ 当前持有锁的线程 │ │ (只有一个) │ │ │ │ _EntryList ──→ 排队等锁的线程队列 │ │ (都睡着等被唤醒) │ │ │ │ _WaitSet ────→ 调用了 wait() 的线程 │ │ (主动等待的线程) │ │ │ │ _count ──────→ 重入计数 │ │ (同一线程进入几次) │ └─────────────────────────────────────────┘加锁过程Thread C 想获取 obj 的锁 第1步JVM 检查 obj 的 Mark Word发现是重量级锁标志位 10 Mark Word → 指向一个 ObjectMonitor 第2步检查 Monitor 的 _owner ├─ null → CAS 设置自己为 _owner拿到锁 ✓ ├─ 就是自己 → 重入_count拿到锁 ✓ └─ 其他线程 → Thread C 进入阻塞流程 ↓ 第3步阻塞流程开销大就在这里 ① Thread C 被放入 _EntryList 队列 ② Thread C 调用操作系统的 mutex_lock() 系统调用 ③ OS 把 Thread C 挂起从运行态 → 阻塞态 ④ 这涉及 用户态 ↔ 内核态 切换成本极高 ⑤ Thread C 就睡着了不消耗 CPU但上下文切换代价大释放锁过程Thread A 退出 synchronized 第1步设置 _owner null 第2步从 _EntryList 中唤醒一个等待的线程 第3步被唤醒的线程重新竞争锁 不是直接给它而是让它和其他线程公平竞争