建设部执业注册中心网站,wordpress中文文档下载,广东网站优化公司,建站大师Synchronized如何实现同步/互斥的效果#xff1f; monitorenter#xff1a; 将锁对象对象头中Mark Word的前30bit替换成指向操作系统中与其关联的monitor对象#xff0c;将锁记录位状态改为10 monitorexit#xff1a; 将锁对象对象头中Mark Word进行重置#xff0c;重新恢…Synchronized如何实现同步/互斥的效果 monitorenter 将锁对象对象头中Mark Word的前30bit替换成指向操作系统中与其关联的monitor对象将锁记录位状态改为10 monitorexit 将锁对象对象头中Mark Word进行重置重新恢复成原来的样子并通过在EntryList中等待的线程来继续竞争 工作流程 开始时 Monitor 中 Owner 为 null 当 Thread-2 执行 synchronized(obj) 就会将 Monitor 的所有者 Owner 置为 Thread-2Monitor 中只能有一个 Ownerobj 对象的 Mark Word 指向 Monitor把对象原有的 MarkWord 存入线程栈中的锁记录中轻量级锁部分详解 在 Thread-2 上锁的过程Thread-3、Thread-4、Thread-5 也执行 synchronized(obj)就会进入 EntryList BLOCKED双向链表 Thread-2 执行完同步代码块的内容根据 obj 对象头中 Monitor 地址寻找设置 Owner 为空把线程栈的锁记录中的对象头的值设置回 MarkWord 唤醒 EntryList 中等待的线程来竞争锁竞争是非公平的如果这时有新的线程想要获取锁可能直接就抢占到了阻塞队列的线程就会继续阻塞 WaitSet 中的 Thread-0是以前获得过锁但条件不满足进入 WAITING 状态的线程wait-notify 机制 注意
synchronized 必须是进入同一个对象的 Monitor 才有上述的效果不加 synchronized 的对象不会关联监视器不遵从以上规则
锁状态改变之后hashcode分代年龄都去哪里了
轻量级锁原先MarkWord中这些信息都会被存储到 线程栈帧中的锁记录存储锁对象的Mark Word拷贝中。
重量级锁当锁对象的Mark Word被重置之后hashcode 分代年龄都会被存储到操作系统的monitor对象中然后当monitorexit的时候会从monitor对象中取出此类信息然后重置Mark Word
注意 synchronized中代码抛出了异常在字节码层面会发现会将锁正确的释放。不会造成死锁等情况即使出现了异常还是会正常的帮我们monitorexit重置Mark Word和唤醒entryList中等待线程
Synchronized锁升级原理
无锁 - 偏向锁 - 轻量级锁 - 重量级锁 // 随着竞争的增加只能锁升级不能降级
无锁状态001 ---- 偏向锁101 --偏向撤销– 轻量级锁00 – 锁膨胀CAS失败说明有竞争情况 -- 重量级锁 偏向锁
偏向锁的思想是偏向于让第一个获取锁对象的线程这个线程之后重新获取该锁不再需要同步操作 当锁对象第一次被线程获得的时候进入偏向状态标记为 101同时使用 CAS 操作将线程 ID 记录到 Mark Word。如果 CAS 操作成功这个线程以后进入这个锁相关的同步块查看这个线程 ID 是自己的就表示没有竞争就不需要再进行任何同步操作 当有另外一个线程去尝试获取这个锁对象时偏向状态就宣告结束此时撤销偏向Revoke Bias后恢复到未锁定或轻量级锁状态 一个对象创建时 如果开启了偏向锁默认开启那么对象创建后MarkWord 值为 0x05 即最后 3 位为 101thread、epoch、age 都为 0 偏向锁是默认是延迟的不会在程序启动时立即生效如果想避免延迟可以加 VM 参数 -XX:BiasedLockingStartupDelay0 来禁用延迟。JDK 8 延迟 4s 开启偏向锁原因在刚开始执行代码时会有好多线程来抢锁如果开偏向锁效率反而降低 当一个对象已经计算过 hashCode就再也无法进入偏向状态了 添加 VM 参数 -XX:-UseBiasedLocking 禁用偏向锁
撤销偏向锁的状态
调用对象的 hashCode偏向锁的对象 MarkWord 中存储的是线程 id调用 hashCode 导致偏向锁被撤销当有其它线程使用偏向锁对象时会将偏向锁升级为轻量级锁调用 wait/notify需要申请 Monitor进入 WaitSet
批量撤销如果对象被多个线程访问但没有竞争这时偏向了线程 T1 的对象仍有机会重新偏向 T2重偏向会重置对象的 Thread ID 批量重偏向当撤销偏向锁阈值超过 20 次后JVM 会觉得是不是偏向错了于是在给这些对象加锁时重新偏向至加锁线程 批量撤销当撤销偏向锁阈值超过 40 次后JVM 会觉得自己确实偏向错了根本就不该偏向于是整个类的所有对象都会变为不可偏向的新建的对象也是不可偏向的
轻量级锁
工作流程中是CAS操作尝试将对象头Mark Word的前30bit替换成指向获取到锁线程栈帧内部的Lock Record锁记录用于存储锁对象MarkWord的拷贝的指针
获取成功则执行代码块CAS失败虚拟机会检查当前锁记录中是否是当前线程的锁记录如果是则出现锁重入现象会在当前虚拟机栈中开辟新的栈帧中的锁记录新的锁记录不会存储拷贝Mark Word而是用于作为重入的计数如果不是说明有竞争的线程了而轻量级锁的核心就是认为“多把锁场景下。不会存在竞争的情况。
当有两个及以上线程来获取对象锁的时候这时候轻量级锁会进行膨胀升级为重量级锁并且竞争的线程会被放入该锁对象对应的monitor对象的EntryList中处于阻塞等待状态CAS操作尝试将锁对象的Mark Word恢复回去 一个对象有多个线程要加锁但加锁的时间是错开的没有竞争可以使用轻量级锁来优化轻量级锁对使用者是透明的不可见
可重入锁线程可以进入任何一个它已经拥有的锁所同步着的代码块可重入锁最大的作用是避免死锁
轻量级锁在没有竞争时锁重入时每次重入仍然需要执行 CAS 操作Java 6 才引入的偏向锁来优化
锁重入实例
static final Object obj new Object();
public static void method1() {synchronized( obj ) {// 同步块 Amethod2();}
}
public static void method2() {synchronized( obj ) {// 同步块 B}
}创建锁记录Lock Record对象每个线程的栈帧都会包含一个锁记录的结构不可见存储锁定对象的 Mark Word 让锁记录中 Object reference 指向锁住的对象并尝试用 CAS 替换 Object 的 Mark Word将 Mark Word 的值存入锁记录 如果 CAS 替换成功原 object 是01 无锁对象头中存储了锁记录地址和状态 00轻量级锁 表示由该线程给对象加锁 如果 CAS 失败有两种情况 如果是其它线程已经持有了该 Object 的轻量级锁这时表明有竞争进入锁膨胀过程如果是线程自己执行了 synchronized 锁重入就添加一条 Lock Record 作为重入的计数 当退出 synchronized 代码块解锁时 如果有取值为 null 的锁记录表示有重入这时重置锁记录表示重入计数减 1如果锁记录的值不为 null这时使用 CAS 将 Mark Word 的值恢复给对象头 成功则解锁成功失败说明轻量级锁进行了锁膨胀或已经升级为重量级锁进入重量级锁解锁流程
锁膨胀
在尝试加轻量级锁的过程中CAS 操作无法成功可能是其它线程为此对象加上了轻量级锁有竞争这时需要进行锁膨胀将轻量级锁变为重量级锁 当 Thread-1 进行轻量级加锁时Thread-0 已经对该对象加了轻量级锁 Thread-1 加轻量级锁失败进入锁膨胀流程为 Object 对象申请 Monitor 锁通过 Object 对象头获取到持锁线程将 Monitor 的 Owner 置为 Thread-0将 Object 的对象头指向重量级锁地址然后自己进入 Monitor 的 EntryList BLOCKED 当 Thread-0 退出同步块解锁时使用 CAS 将 Mark Word 的值恢复给对象头失败这时进入重量级解锁流程即按照 Monitor 地址找到 Monitor 对象设置 Owner 为 null唤醒 EntryList 中 BLOCKED 线程
锁优化
自旋锁
重量级锁竞争时尝试获取锁的线程不会立即阻塞可以使用自旋默认 10 次来进行优化采用循环的方式去尝试获取锁
注意
自旋占用 CPU 时间单核 CPU 自旋就是浪费时间因为同一时刻只能运行一个线程多核 CPU 自旋才能发挥优势自旋失败的线程会进入阻塞状态
优点不会进入阻塞状态减少线程上下文切换的消耗
缺点当自旋的线程越来越多时会不断的消耗 CPU 资源
自旋锁说明
在 Java 6 之后自旋锁是自适应的比如对象刚刚的一次自旋操作成功过那么认为这次自旋成功的可能性会高就多自旋几次反之就少自旋甚至不自旋比较智能Java 7 之后不能控制是否开启自旋功能由 JVM 控制
锁消除
锁消除是指对于被检测出不可能存在竞争的共享数据的锁进行消除这是 JVM 即时编译器的优化
锁消除主要是通过逃逸分析来支持如果堆上的共享数据不可能逃逸出去被其它线程访问到那么就可以把它们当成私有数据对待也就可以将它们的锁进行消除同步消除JVM 逃逸分析 -XX:-EliminateLocks
锁粗化
对相同对象多次加锁导致线程发生多次重入频繁的加锁操作就会导致性能损耗可以使用锁粗化方式优化
如果虚拟机探测到一串的操作都对同一个对象加锁将会把加锁的范围扩展粗化到整个操作序列的外部 一些看起来没有加锁的代码其实隐式的加了很多锁 public static String concatString(String s1, String s2, String s3) {return s1 s2 s3;
}String 是一个不可变的类编译器会对 String 的拼接自动优化。在 JDK 1.5 之前转化为 StringBuffer 对象的连续 append() 操作每个 append() 方法中都有一个同步块 public static String concatString(String s1, String s2, String s3) {StringBuffer sb new StringBuffer();sb.append(s1);sb.append(s2);sb.append(s3);return sb.toString();
}扩展到第一个 append() 操作之前直至最后一个 append() 操作之后只需要加锁一次就可以