synchronized
核心特性
- 可重入(同一线程可以多次获取同一个对象的锁, 不会因为自己已经持有的锁陷入阻塞)
- 互斥 (同一时间内只有一个线程持有锁, 线程安全的核心)
- 悲观 (假设线程的冲突概率高, 直接加锁, 悲观策略,让synchronized 在竞争激烈的情况下很稳定)
针对悲观策略 JDK1.6 之后的重大升级
在JDK1.6 之前 synchronized就是 纯悲观策略, 不管竞争是否激烈, 只要进入代码块, 直接加上重量级锁(阻塞与唤醒)。
JDK1.6之后, 就变成了根据竞争的激烈程度进行升级。
一开始是偏向锁, 只有一个线程的时候, 系统会在对象头记录 这个线程ID, 下次再进入同步代码块, 就不用申请锁了, 直接就能执行。
当有第二个线程 想要竞争锁的时候, 偏向锁 就会升级成轻量级锁, 即虚拟机 发现第二个线程要锁, 且对象头里记录的ID不是第二个线程的。
这时每个线程都会复制一份对象头 里的锁信息, 然后 双方都尝试用CAS操作 去 修改 对象头 里的 锁标记 成 自己的线程ID, 当修改成功 就拿到锁了。
具体的流程是, 在竞争之前 , 会先复制一份 锁标记的信息 , 然后在自己的存储区域 计算出 自己的锁记录地址 ,比如 0x456,更新 锁标记信息, 然后 对 对象头 里的锁标记 信息 执行CAS操作, 把自己的 新的 锁标记信息 更新到对象头上, 执行成功 就拿到锁了。
如果没成功, 就会自旋式的请求锁.(补充一个场景, 如果线程B拿到了锁, 锁标记中的锁记录地址是 0x456, 如果线程C进来 复制了 锁信息 ,是0x456, CAS操作, C虽然 确实 锁记录地址 与预期一致, 但是会因为线程B还在持有锁 而被阻止, 所以会失败, 知道线程B释放锁, 把 锁标记 重新改为默认的mark word副本)。
当同时 自旋的线程 或者 线程的自旋的次数 过多。 就会升级为重量级锁。
对象头里边 会存储一个 指向monitor对象的指针, 这个monitor对象就像一个锁管家 ,当线程竞争失败, 就会被monitor对象标记为阻塞状态,放入等待队列。 不会占用CPU资源。 需要注意的是 锁记录地址 转移到了monitor对象里边。
卧槽今天北京太冷了, 顶不住。 后面其实还有几个锁的,后面的篇章再说吧