当前位置: 首页 > news >正文

网站制作的流程有哪些WordPress文章ajax

网站制作的流程有哪些,WordPress文章ajax,wordpress短链接清除,贵州中英文网站制作并发锁机制之synchronized i/i--引起的线程安全问题分析原因分析i的JVM字节码指令i--的JVM 字节码指令结论 解决方案 synchronized的使用加锁方式使用synchronized解决之前的共享问题方式一方式二 synchronized底层实现原理分析查看synchronized的字节码指令序列重量级锁实现之… 并发锁机制之synchronized i/i--引起的线程安全问题分析原因分析i的JVM字节码指令i--的JVM 字节码指令结论 解决方案 synchronized的使用加锁方式使用synchronized解决之前的共享问题方式一方式二 synchronized底层实现原理分析查看synchronized的字节码指令序列重量级锁实现之Monitor管程/监视器机制详解Monitor设计思路MESA模型分析ObjectMonitor数据结构分析 重量级锁实现原理 重量级锁的优化策略锁粗化锁消除CAS自旋优化轻量级锁轻量级锁是否存在自旋问题分析 偏向锁锁升级的过程 synchronized锁升级详解sychronized多种锁状态设计详解对象的内存布局对象头详解使用JOL工具查看内存布局 Mark Word是如何记录锁状态的 锁升级场景实验轻量级锁详解轻量级锁实现原理 偏向锁详解偏向锁匿名偏向状态偏向锁延迟偏向偏向锁状态跟踪实验对象调用了hashCode不会开启偏向锁偏向锁撤销场景偏向锁撤销之调用对象HashCode偏向锁撤销之调用wait/notify 偏向锁批量重偏向批量撤销实现原理应用场景批量重偏向实验批量撤销实验总结 锁升级的流程分析 i/i–引起的线程安全问题分析 两个线程对初始值为0的静态变量一个做自增一个做自减各做5000次结果是0吗? public class SyncDemo {private static int counter 0;public static void increment() {counter;}public static void decrement() {counter--;}public static void main(String[] args) throws InterruptedException {Thread t1 new Thread(() - {for (int i 0; i 5000; i) {increment();}}, t1);Thread t2 new Thread(() - {for (int i 0; i 5000; i) {decrement();}}, t2);t1.start();t2.start();t1.join();t2.join();//思考 counterlog.info({}, counter);} } 原因分析 以上的结果可能是正数、负数、零。为什么呢因为 Java 中对静态变量的自增自减并不是原子操作。 我们可以查看i和i–i为静态变量的JVM字节码指令可以在idea中安装一个jclasslib插件 i的JVM字节码指令 getstatic i // 获取静态变量i的值并将其值压入栈顶 iconst_1 // 将int型常量1压入栈顶 iadd // 将栈顶两int型数值相加并将结果压入栈顶 putstatic i // 将结果赋值给静态变量i i–的JVM 字节码指令 getstatic i // 获取静态变量i的值并将其值压入栈顶 iconst_1 // 将int型常量1压入栈顶 isub // 将栈顶两int型数值相减并将结果压入栈顶 putstatic i // 将结果赋值给静态变量i 如果是单线程运行以上8行代码是顺序执行不会交错没有问题。但多线程下这8行代码可能交错运行 结论 一个程序运行多个线程本身是没有问题的问题出在多个线程访问共享资源 多个线程读共享资源其实也没有问题在多个线程对共享资源读写操作时发生指令交错就会出现问题 解决方案 一段代码块内如果存在对共享资源的多线程读写操作称这段代码块为临界区其共享资源为临界资源。 多个线程在临界区内执行由于代码的执行序列不同而导致结果无法预测称之为发生了竞态条件。 //临界资源 private static int counter 0;public static void increment() { //临界区counter; }public static void decrement() {//临界区counter--; } 为了避免临界区的竞态条件发生有多种手段可以达到目的 阻塞式的解决方案synchronizedLock非阻塞式的解决方案原子变量 synchronized的使用 synchronized同步块是Java提供的一种原子性内置锁Java中的每个对象都可以把它当作一个同步锁来使用这些Java内置的使用者看不到的锁被称为内置锁也叫作监视器锁。 加锁方式 使用synchronized解决之前的共享问题 方式一 public static synchronized void increment() {counter; }public static synchronized void decrement() {counter--; } 方式二 private static String lock ;public static void increment() {synchronized (lock){counter;} }public static void decrement() {synchronized (lock) {counter--;} } synchronized实际是用对象锁保证了临界区内代码的原子性 synchronized底层实现原理分析 synchronized是JVM内置锁基于Monitor机制实现依赖底层操作系统的互斥原语Mutex互斥量它是一个重量级锁性能较低。 查看synchronized的字节码指令序列 Method access and property flags 同步方法是通过方法中的access_flags中设置ACC_SYNCHRONIZED标志来实现同步代码块是通过monitorenter和monitorexit来实现。 重量级锁实现之Monitor管程/监视器机制详解 Monitor直译为“监视器”而操作系统领域一般翻译为“管程”。管程是指管理共享变量以及对共享变量操作的过程让它们支持并发。在Java 1.5之前Java语言提供的唯一并发语言就是管程Java 1.5之后提供的SDK并发包也是以管程为基础的。除了Java之外C/C、C#等高级语言也都是支持管程的。synchronized关键字和wait()、notify()、notifyAll()这三个方法是Java中实现管程技术的组成部分。 Monitor设计思路 MESA模型分析 在管程的发展史上先后出现过三种不同的管程模型分别是Hasen模型、Hoare模型和MESA模型。现在正在广泛使用的是MESA模型。下面我们便介绍MESA模型 管程中引入了条件变量的概念而且每个条件变量都对应有一个等待队列。条件变量和等待队列的作用是解决线程之间的同步问题。 Java参考了MESA模型语言内置的管程synchronized对MESA模型进行了精简。MESA模型中条件变量可以有多个Java语言内置的管程里只有一个条件变量。模型如下图所示: Slf4j public class WaitDemo {final static Object obj new Object();public static void main(String[] args) throws InterruptedException {new Thread(() - {log.debug(t1开始执行....);synchronized (obj) {log.debug(t1获取锁....);try {// 让线程在obj上一直等待下去obj.wait();} catch (InterruptedException e) {e.printStackTrace();}log.debug(t1执行完成....);}},t1).start();new Thread(() - {log.debug(t2开始执行....);synchronized (obj) {log.debug(t2获取锁....);try {// 让线程在obj上一直等待下去obj.wait();} catch (InterruptedException e) {e.printStackTrace();}log.debug(t2执行完成....);}},t2).start();// 主线程两秒后执行Thread.sleep(2000);log.debug(准备获取锁去唤醒 obj 上阻塞的线程);synchronized (obj) {// 唤醒obj上一个线程//obj.notify();// 唤醒obj上所有等待线程obj.notifyAll();log.debug(唤醒 obj 上阻塞的线程);}}} ObjectMonitor数据结构分析 java.lang.Object类定义了wait()notify()notifyAll()方法这些方法的具体实现依赖于ObjectMonitor实现这是JVM内部基于C实现的一套机制。 ObjectMonitor其主要数据结构如下hotspot源码ObjectMonitor.hpp: ObjectMonitor() {_header NULL; //对象头 markOop_count 0; _waiters 0, _recursions 0; // 锁的重入次数 _object NULL; //存储锁对象_owner NULL; // 标识拥有该monitor的线程当前获取锁的线程 _WaitSet NULL; // 等待线程调用wait组成的双向循环链表_WaitSet是第一个节点_WaitSetLock 0 ; _Responsible NULL ;_succ NULL ;_cxq NULL ; //多线程竞争锁会先存到这个单向链表中 FILO栈结构FreeNext NULL ;_EntryList NULL ; //存放在进入或重新进入时被阻塞(blocked)的线程 (也是存竞争锁失败的线程)_SpinFreq 0 ;_SpinClock 0 ;OwnerIsThread 0 ;_previous_owner_tid 0;} 重量级锁实现原理 synchronized底层是利用monitor对象CAS和mutex互斥锁来实现的内部会有等待队列(cxq和EntryList)和条件等待队列(waitSet)来存放相应阻塞的线程。未竞争到锁的线程存储到等待队列中获得锁的线程调用wait后便存放在条件等待队列中解锁和notify都会唤醒相应队列中的等待线程来争抢锁。然后由于阻塞和唤醒依赖于底层的操作系统实现系统调用存在用户态与内核态之间的切换所以有较高的开销因此称之为重量级锁。 在获取锁时是将当前线程插入到cxq的头部而释放锁时默认策略QMode0是如果EntryList为空则将cxq中的元素按原有顺序插入到EntryList并唤醒第一个线程也就是当EntryList为空时是后来的线程先获取锁。_EntryList不为空直接从_EntryList中唤醒线程。 为什么会有_cxq和_EntryList两个列表来放线程 因为会有多个线程会同时竞争锁所以搞了个_cxq这个单向链表基于CAS来hold住这些并发然后另外搞一个_EntryList这个双向链表来在每次唤醒的时候搬迁一些线程节点降低_cxq的尾部竞争。 重量级锁的优化策略 JVM内置锁在1.5之后版本做了重大的优化如锁粗化Lock Coarsening、锁消除Lock Elimination、轻量级锁Lightweight Locking、偏向锁Biased Locking、自适应自旋Adaptive Spinning等技术来减少锁操作的开销内置锁的并发性能已经基本与Lock持平。 锁粗化 锁粗化简单来说就是将多个连续的锁扩展为一个更大范围的锁。也就是说如果 JVM 检测到有连续的对同一对象的加锁、解锁操作就会把这些加锁、解锁操作合并为对这段区域进行一次连续的加锁和解锁。 synchronized (lock) {// 代码块 1 } // 无关代码 synchronized (lock) {// 代码块 2 } // JVM 在运行时可能会选择将上述两个小的同步块合并形成一个大的同步块 synchronized (lock) {// 代码块 1// 无关代码// 代码块 2 } 为什么锁粗化有效 加锁和解锁操作本身也会带来一定的性能开销因为每次加锁和解锁都可能会涉及到线程切换、线程调度等开销。如果有大量小的同步块频繁地进行加锁和解锁那么这部分开销可能会变得很大从而降低程序的执行效率。 通过锁粗化可以将多次加锁和解锁操作减少到一次从而减少这部分开销提高程序的运行效率。 如何在代码中实现锁粗化 在代码层面上我们并不能直接控制JVM进行锁粗化因为这是JVM在运行时动态进行的优化。不过我们可以在编写代码时尽量减少不必要的同步块避免频繁加锁和解锁。这样就为JVM的锁粗化优化提供了可能。 代码示例 StringBuffer buffer new StringBuffer(); /*** 锁粗化*/ public void append(){buffer.append(aaa).append( bbb).append( ccc); } 上述代码每次调用buffer.append方法都需要加锁和解锁如果JVM检测到有一连串的对同一个对象加锁和解锁的操作就会将其合并成一次范围更大的加锁和解锁操作即在第一次append方法时进行加锁最后一次append方法结束后进行解锁。 锁粗化是JVM提供的一种优化手段能够有效地提高并发编程的效率。在我们编写并发代码时应当注意同步块的使用尽量减少不必要的加锁和解锁从而使得锁粗化技术能够发挥作用。 锁消除 锁消除主要应用在没有多线程竞争的情况下。具体来说当一个数据仅在一个线程中使用或者说这个数据的作用域仅限于一个线程时这个线程对该数据的所有操作都不需要加锁。在Java HotSpot VM中这种优化主要是通过逃逸分析Escape Analysis来实现的。 为什么锁消除有效 锁消除之所以有效是因为它消除了不必要的锁竞争从而减少了线程切换和线程调度带来的性能开销。当数据仅在单个线程中使用时对此数据的所有操作都不需要同步。在这种情况下锁操作不仅不会增加安全性反而会因为增加了额外的执行开销而降低程序的运行效率。 如何在代码中实现锁消除 在代码层面上我们无法直接控制JVM进行锁消除优化这是由JVM的JIT编译器在运行时动态完成的。但我们可以通过编写高质量的代码使JIT编译器更容易识别出可以进行锁消除的场景。例如 public class LockEliminationTest {/*** 锁消除* -XX:EliminateLocks 开启锁消除(jdk8默认开启* -XX:-EliminateLocks 关闭锁消除* param str1* param str2*/public void append(String str1, String str2) {StringBuffer stringBuffer new StringBuffer();stringBuffer.append(str1).append(str2);}public static void main(String[] args) throws InterruptedException {LockEliminationTest demo new LockEliminationTest();long start System.currentTimeMillis();for (int i 0; i 100000000; i) {demo.append(aaa, bbb);}long end System.currentTimeMillis();System.out.println(执行时间 (end - start) ms);}} StringBuffer的append是个同步方法但是append方法中的 StringBuffer 属于一个局部变量不可能从该方法中逃逸出去因此其实这过程是线程安全的可以将锁消除。因此JIT 编译器会发现这种情况并自动消除 append 操作中的锁竞争。 测试结果 关闭锁消除执行时间4688 ms开启锁消除执行时间2601 ms CAS自旋优化 重量级锁竞争的时候还可以使用自旋来进行优化如果当前线程自旋成功即这时候持锁线程已经退出了同步块释放了锁这时当前线程就可以避免阻塞。 自旋会占用CPU时间单核CPU自旋就是浪费多核CPU自旋才能发挥优势。在Java 6之后自旋是自适应的比如对象刚刚的一次自旋操作成功过那么认为这次自旋成功的可能性会高就多自旋几次反之就少自旋甚至不自旋比较智能。可以使用-XX:UseSpinning 参数来开启自旋锁使用-XX:PreBlockSpin 参数来设置自旋锁的等待次数。Java 7之后不能控制是否开启自旋功能自旋锁的参数被取消自旋锁总是会执行自旋次数也由虚拟机自行调整。 注意自旋的目的是为了减少线程挂起的次数尽量避免直接挂起线程挂起操作涉及系统调用存在用户态和内核态切换这才是重量级锁最大的开销 轻量级锁 我们再思考一下是否有这样的场景多个线程都是在不同的时间段来请求同一把锁此时根本就用不需要阻塞线程连monitor对象都不需要所以就引入了轻量级锁这个概念避免了系统调用减少了开销。 在锁竞争不激烈的情况下这种场景还是很常见的可能是常态所以轻量级锁的引入很有必要。 轻量级锁是否存在自旋问题分析 错误理解轻量级锁加锁失败会自旋失败一定次数后会膨胀升级为重量级锁 正确理解轻量级锁不存在自旋只有重量级锁加锁失败才会自旋。重量级锁加锁失败会多次尝试cas和自适应自旋如果一直加锁失败就会阻塞当前线程等待唤醒 轻量级锁竞争没有自旋的原因其实是其设计并不是用于处理过于激烈的竞争场景而是为了应对线程之间交替获取锁的场景。 偏向锁 我们再思考一下是否有这样的场景一开始一直只有一个线程持有这个锁也不会有其他线程来竞争此时频繁的CAS是没有必要的CAS也是有开销的。所以 synchronized就搞了个偏向锁就是偏向一个线程那么这个线程就可以直接获得锁。对于没有锁竞争的场合偏向锁有很好的优化效果可以消除锁重入CAS操作带来的开销。 锁升级的过程 synchronized锁升级详解 sychronized多种锁状态设计详解 对象的内存布局 Hotspot虚拟机中对象在内存中存储的布局可以分为三块区域对象头Header、实例数据Instance Data和对齐填充Padding 对象头比如hash码对象所属的年代对象锁锁状态标志偏向锁线程ID偏向时间数组长度数组对象才有等。实例数据存放类的属性数据信息包括父类的属性信息对齐填充由于虚拟机要求对象起始地址必须是8字节的整数倍。填充数据不是必须存在的仅仅是为了字节对齐。 对象头详解 HotSpot虚拟机的对象头包括 Mark Word 用于存储对象自身的运行时数据如哈希码HashCode、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等这部分数据的长度在32位和64位的虚拟机中分别为32bit和64bit官方称它为“Mark Word”。 Klass Pointer 对象头的另外一部分是klass类型指针即对象指向它的类元数据的指针虚拟机通过这个指针来确定这个对象是哪个类的实例。32位4字节64位开启指针压缩或最大堆内存32g时4字节否则8字节。jdk1.8默认开启指针压缩后为4字节当在JVM参数中关闭指针压缩-XX:-UseCompressedOops后长度为8字节。 数组长度只有数组对象有 如果对象是一个数组, 那在对象头中还必须有一块数据用于记录数组长度。 4字节 使用JOL工具查看内存布局 分享一个可以查看普通java对象的内部布局工具JOL(JAVA OBJECT LAYOUT)使用此工具可以查看new出来的一个java对象的内部布局,以及一个普通的java对象占用多少字节。 1、引入maven依赖 !-- 查看Java 对象布局、大小工具 -- dependencygroupIdorg.openjdk.jol/groupIdartifactIdjol-core/artifactIdversion0.10/version /dependency 2、使用方法 //查看对象内部信息 System.out.println(ClassLayout.parseInstance(obj).toPrintable()); 测试 public static void main(String[] args) throws InterruptedException {Object obj new Object();//查看对象内部信息System.out.println(ClassLayout.parseInstance(obj).toPrintable()); } 利用jol查看64位系统java对象空对象默认开启指针压缩总大小显示16字节前12字节为对象头 OFFSET偏移地址单位字节SIZE占用的内存大小单位为字节TYPE DESCRIPTION类型描述其中object header为对象头VALUE对应内存中当前存储的值二进制32位 关闭指针压缩后对象头为16字节-XX:-UseCompressedOops Mark Word是如何记录锁状态的 Hotspot通过markOop类型实现Mark Word具体实现位于markOop.hpp文件中。由于对象需要存储的运行时数据很多考虑到虚拟机的内存使用markOop被设计成一个非固定的数据结构以便在极小的空间存储尽量多的数据根据对象的状态复用自己的存储空间。 简单点理解就是MarkWord结构搞得这么复杂是因为需要节省内存让同一个内存区域在不同阶段有不同的用处。 Mark Word的锁标记结构 // 32 bits: // -------- // hash:25 ------------| age:4 biased_lock:1 lock:2 (normal object) // JavaThread*:23 epoch:2 age:4 biased_lock:1 lock:2 (biased object) // size:32 ------------------------------------------| (CMS free block) // PromotedObject*:29 ----------| promo_bits:3 -----| (CMS promoted object) // // 64 bits: // -------- // unused:25 hash:31 --| unused:1 age:4 biased_lock:1 lock:2 (normal object) // JavaThread*:54 epoch:2 unused:1 age:4 biased_lock:1 lock:2 (biased object) // PromotedObject*:61 ---------------------| promo_bits:3 -----| (CMS promoted object) // size:64 -----------------------------------------------------| (CMS free block) // // unused:25 hash:31 --| cms_free:1 age:4 biased_lock:1 lock:2 (COOPs normal object) // JavaThread*:54 epoch:2 cms_free:1 age:4 biased_lock:1 lock:2 (COOPs biased object) // narrowOop:32 unused:24 cms_free:1 unused:4 promo_bits:3 -----| (COOPs CMS promoted object) // unused:21 size:35 --| cms_free:1 unused:7 ------------------| (COOPs CMS free block)。。。。。。 // [JavaThread* | epoch | age | 1 | 01] lock is biased toward given thread // [0 | epoch | age | 1 | 01] lock is anonymously biased // // - the two lock bits are used to describe three states: locked/unlocked and monitor. // // [ptr | 00] locked ptr points to real header on stack // [header | 0 | 01] unlocked regular object header // [ptr | 10] monitor inflated lock (header is wapped out) // [ptr | 11] marked used by markSweep to mark an object // not valid at any other time 32位JVM下的对象结构描述 64位JVM下的对象结构描述 hash:保存对象的哈希码。运行期间调用System.identityHashCode()来计算延迟计算并把结果赋值到这里。age:保存对象的分代年龄。表示对象被GC的次数当该次数到达阈值的时候对象就会转移到老年代。分代年龄占4字节二进制1111转成十进制就是15分代年龄最大15biased_lock:偏向锁标识位。由于无锁和偏向锁的锁标识都是01没办法区分这里引入一位的偏向锁标识位。lock:锁状态标识位。区分锁状态比如11时表示对象待GC回收状态, 只有最后2位锁标识(11)有效。JavaThread*:保存持有偏向锁的线程ID。偏向模式的时候当某个线程持有对象的时候对象这里就会被置为该线程的ID。 在后面的操作中就无需再进行尝试获取锁的动作。这个线程ID并不是JVM分配的线程ID号和Java Thread中的ID是两个概念。epoch:偏向锁撤销的计数器可用于偏向锁批量重偏向和批量撤销的判断依据。ptr_to_lock_record:轻量级锁状态下指向栈中锁记录的指针。当锁获取是无竞争时JVM使用原子操作而不是OS互斥这种技术称为轻量级锁定。在轻量级锁定的情况下JVM通过CAS操作在对象的Mark Word中设置指向锁记录的指针。ptr_to_heavyweight_monitor:重量级锁状态下指向对象监视器Monitor的指针。如果两个不同的线程同时在同一个对象上竞争则必须将轻量级锁定升级到Monitor以管理等待的线程。在重量级锁定的情况下JVM在对象的ptr_to_heavyweight_monitor设置指向Monitor的指针 锁升级场景实验 示例代码:演示锁升级的过程 public class LockUpgrade {public static void main(String[] args) throws InterruptedException {User userTemp new User();System.out.println(无锁状态001 ClassLayout.parseInstance(userTemp).toPrintable());/* jvm默认延时4s自动开启偏向锁可通过-XX:BiasedLockingStartupDelay0取消延时;如果不要偏向锁可通过-XX:-UseBiasedLockingfalse来设置*/Thread.sleep(5000);User user new User();System.out.println(启用偏向锁(101): ClassLayout.parseInstance(user).toPrintable());for(int i0;i2;i){synchronized (user){System.out.println(偏向锁(101)(带线程id): ClassLayout.parseInstance(user).toPrintable());}System.out.println(偏向锁释放(101)(带线程id) ClassLayout.parseInstance(user).toPrintable());}new Thread(new Runnable() {Overridepublic void run() {synchronized (user){System.out.println(轻量级锁(00): ClassLayout.parseInstance(user).toPrintable());try {System.out.println(睡眠3秒);Thread.sleep(3000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(轻量级锁---重量级锁(10): ClassLayout.parseInstance(user).toPrintable());}}}).start();Thread.sleep(1000);System.out.println(重量级锁(10): ClassLayout.parseInstance(user).toPrintable());new Thread(new Runnable() {Overridepublic void run() {synchronized (user) {System.out.println(重量级锁(10): ClassLayout.parseInstance(user).toPrintable());}}}).start();Thread.sleep(5000);System.out.println(无锁状态(001): ClassLayout.parseInstance(user).toPrintable());} } 重量级锁释放之后变为无锁此时有新的线程来调用同步块会获取什么锁 轻量锁 轻量级锁详解 轻量级锁所适应的场景是线程交替执行同步块的场合如果存在同一时间多个线程访问同一把锁的场合就会导致轻量级锁膨胀为重量级锁。 轻量级锁实现原理 在介绍轻量级锁的原理之前再看看之前MarkWord图。 轻量级锁操作的就是对象头的MarkWord。 如果判断当前处于无锁状态会在当前线程栈的当前栈帧中划出一块叫LockRecord的区域然后把锁对象的MarkWord拷贝一份到LockRecord中称之为dhw(就是那个set_displaced_header方法执行的)里。 然后通过CAS把锁对象头指向这个LockRecord。 轻量级锁的加锁过程 如果当前是有锁状态并且是当前线程持有的则将null放到dhw中这是重入锁的逻辑。 我们再看下轻量级锁解锁的逻辑 逻辑还是很简单的就是要把当前栈帧中LockRecord存储的markworddhw通过CAS换回到对象头中。如果获取到的dhw是null说明此时是重入的所以直接返回即可否则就是利用CAS换如果CAS失败说明此时有竞争那么就膨胀 偏向锁详解 偏向锁是一种针对加锁操作的优化手段经过研究发现在大多数情况下锁不仅不存在多线程竞争而且总是由同一线程多次获得因此为了消除数据在无竞争情况下锁重入CAS操作的开销而引入偏向锁。对于没有锁竞争的场合偏向锁有很好的优化效果。 /***StringBuffer内部同步***/ public synchronized int length() { return count; } //System.out.println 无意识的使用锁 public void println(String x) { synchronized (this) {print(x); newLine(); } } 偏向锁匿名偏向状态 当JVM启用了偏向锁模式jdk6默认开启新创建对象的Mark Word中的Thread Id为0说明此时处于可偏向但未偏向任何线程也叫做匿名偏向状态(anonymously biased)。 偏向锁延迟偏向 偏向锁模式存在偏向锁延迟机制HotSpot虚拟机在启动后有个 4s 的延迟才会对每个新建的对象开启偏向锁模式。JVM启动时会进行一系列的复杂活动比如装载配置系统类初始化等等。在这个过程中会使用大量synchronized关键字对对象加锁且这些锁大多数都不是偏向锁。为了减少初始化时间JVM默认延时加载偏向锁。 //关闭延迟开启偏向锁 -XX:BiasedLockingStartupDelay0 //禁止偏向锁 -XX:-UseBiasedLocking //启用偏向锁 -XX:UseBiasedLocking 验证 Slf4j public class LockEscalationDemo{public static void main(String[] args) throws InterruptedException {log.debug(ClassLayout.parseInstance(new Object()).toPrintable());Thread.sleep(4000);log.debug(ClassLayout.parseInstance(new Object()).toPrintable());} } 4s后偏向锁为可偏向或者匿名偏向状态 偏向锁状态跟踪实验 public class LockEscalationDemo {public static void main(String[] args) throws InterruptedException {log.debug(ClassLayout.parseInstance(new Object()).toPrintable());//HotSpot 虚拟机在启动后有个 4s 的延迟才会对每个新建的对象开启偏向锁模式Thread.sleep(4000);Object obj new Object();new Thread(new Runnable() {Overridepublic void run() {log.debug(Thread.currentThread().getName()开始执行。。。\nClassLayout.parseInstance(obj).toPrintable());synchronized (obj){log.debug(Thread.currentThread().getName()获取锁执行中。。。\nClassLayout.parseInstance(obj).toPrintable());}log.debug(Thread.currentThread().getName()释放锁。。。\nClassLayout.parseInstance(obj).toPrintable());}},thread1).start();Thread.sleep(5000);log.debug(ClassLayout.parseInstance(obj).toPrintable());} } 对象调用了hashCode不会开启偏向锁 对于一个对象,其HashCode只会生成一次并保存偏向锁是没有地方保存hashcode的。 偏向锁撤销场景 偏向锁撤销之调用对象HashCode 调用锁对象的obj.hashCode()或System.identityHashCode(obj)方法会导致该对象的偏向锁被撤销。因为对于一个对象其HashCode只会生成一次并保存偏向锁是没有地方保存hashcode的。 轻量级锁会在锁记录中记录 hashCode重量级锁会在 Monitor 中记录 hashCode 当对象处于可偏向也就是线程ID为0和已偏向的状态下调用HashCode计算将会使对象再也无法偏向 当对象可偏向时MarkWord将变成未锁定状态并只能升级成轻量锁当对象正处于偏向锁时调用HashCode将使偏向锁强制升级成重量锁。 偏向锁撤销之调用wait/notify 偏向锁状态执行obj.notify() 会升级为轻量级锁调用obj.wait(timeout) 会升级为重量级锁 synchronized (obj) {// 思考偏向锁执行过程中调用hashcode会发生什么//obj.hashCode();//obj.notify();try {obj.wait(100);} catch (InterruptedException e) {e.printStackTrace();}log.debug(Thread.currentThread().getName() 获取锁执行中。。。\n ClassLayout.parseInstance(obj).toPrintable()); } 偏向锁批量重偏向批量撤销 从偏向锁的加锁解锁过程中可看出当只有一个线程反复进入同步块时偏向锁带来的性能开销基本可以忽略但是当有其他线程尝试获得锁时就需要等到safe point时再将偏向锁撤销为无锁状态或升级为轻量级会消耗一定的性能所以在多线程竞争频繁的情况下偏向锁不仅不能提高性能还会导致性能下降。于是就有了批量重偏向与批量撤销的机制。 实现原理 以class为单位为每个class维护一个偏向锁撤销计数器每一次该class的对象发生偏向撤销操作时该计数器1当这个值达到重偏向阈值默认20时JVM就认为该class的偏向锁有问题因此会进行批量重偏向。 当达到重偏向阈值默认20后假设该class计数器继续增长当其达到批量撤销的阈值后默认40JVM就认为该class的使用场景存在多线程竞争会标记该class为不可偏向之后对于该class的锁直接走轻量级锁的逻辑。 intx BiasedLockingBulkRebiasThreshold 20 //默认偏向锁批量重偏向阈值 intx BiasedLockingBulkRevokeThreshold 40 //默认偏向锁批量撤销阈值 我们可以通过-XX:BiasedLockingBulkRebiasThreshold和-XX:BiasedLockingBulkRevokeThreshold来手动设置阈值 应用场景 批量重偏向bulk rebias机制是为了解决一个线程创建了大量对象并执行了初始的同步操作后来另一个线程也来将这些对象作为锁对象进行操作这样会导致大量的偏向锁撤销操作。 批量撤销bulk revoke机制是为了解决在明显多线程竞争剧烈的场景下使用偏向锁是不合适的。 批量重偏向实验 当撤销偏向锁阈值超过20次后jvm会这样觉得我是不是偏向错了于是会在给这些对象加锁时重新偏向至加锁线程重偏向会重置对象的Thread ID Slf4j public class BiasedLockingTest {//延时产生可偏向对象Thread.sleep(5000);// 创建一个list来存放锁对象ListObject list new ArrayList();// 线程1new Thread(() - {for (int i 0; i 50; i) {// 新建锁对象Object lock new BiasedLockingTest();synchronized (lock) {list.add(lock);}}try {//为了防止JVM线程复用在创建完对象后保持线程thead1状态为存活Thread.sleep(100000);} catch (InterruptedException e) {e.printStackTrace();}}, thead1).start();//睡眠3s钟保证线程thead1创建对象完成Thread.sleep(3000);log.debug(打印thead1list中第20个对象的对象头);log.debug((ClassLayout.parseInstance(list.get(19)).toPrintable()));// 线程2new Thread(() - {for (int i 0; i 40; i) {Object obj list.get(i);synchronized (obj) {if(i15i21||i38){log.debug(thread2-第 (i 1) 次加锁执行中\tClassLayout.parseInstance(obj).toPrintable());}}//if(i17||i19){// log.debug(thread2-第 (i 1) 次释放锁\t// ClassLayout.parseInstance(obj).toPrintable());//}}try {Thread.sleep(100000);} catch (InterruptedException e) {e.printStackTrace();}}, thead2).start();LockSupport.park();} } 测试结果 thread1:创建50个偏向线程thread1的偏向锁 1-50 偏向锁 thread2 1-19 偏向锁撤销升级为轻量级锁 thread1释放锁之后为偏向锁状态 20-40 偏向锁撤销达到阈值20执行了批量重偏向 批量撤销实验 当撤销偏向锁阈值超过40次后jvm会认为不该偏向于是整个类的所有对象都会变为不可偏向的新建的对象也是不可偏向的。 注意时间-XX:BiasedLockingDecayTime25000ms范围内没有达到40次撤销次数清为0重新计时 Slf4j public class BiasedLockingTest {public static void main(String[] args) throws InterruptedException {//延时产生可偏向对象Thread.sleep(5000);// 创建一个list来存放锁对象ListObject list new ArrayList();// 线程1new Thread(() - {for (int i 0; i 40; i) {// 新建锁对象Object lock new BiasedLockingTest();synchronized (lock) {list.add(lock);}}try {//为了防止JVM线程复用在创建完对象后保持线程thead1状态为存活Thread.sleep(100000);} catch (InterruptedException e) {e.printStackTrace();}}, thead1).start();//睡眠3s钟保证线程thead1创建对象完成Thread.sleep(3000);log.debug(打印thead1list中第20个对象的对象头);log.debug((ClassLayout.parseInstance(list.get(19)).toPrintable()));// 线程2new Thread(() - {for (int i 0; i 40; i) {Object obj list.get(i);synchronized (obj) {if(i15i21||i38){log.debug(thread2-第 (i 1) 次加锁执行中\tClassLayout.parseInstance(obj).toPrintable());}}//if(i17||i19){// log.debug(thread2-第 (i 1) 次释放锁\t// ClassLayout.parseInstance(obj).toPrintable());//}}try {Thread.sleep(100000);} catch (InterruptedException e) {e.printStackTrace();}}, thead2).start();Thread.sleep(3000);new Thread(() - {for (int i 0; i 40; i) {Object lock list.get(i);if(i17i21){log.debug(thread3-第 (i 1) 次准备加锁\tClassLayout.parseInstance(lock).toPrintable());}synchronized (lock){if(i17i21){log.debug(thread3-第 (i 1) 次加锁执行中\tClassLayout.parseInstance(lock).toPrintable());}}}},thread3).start();Thread.sleep(3000);log.debug(查看新创建的对象);log.debug((ClassLayout.parseInstance(new BiasedLockingTest()).toPrintable()));LockSupport.park();} } 测试结果 thread3 1-19 从无锁状态直接获取轻量级锁thread2释放锁之后变为无锁状态 20-40 偏向锁撤销升级为轻量级锁thread2释放锁之后为偏向锁状态 达到偏向锁撤销的阈值40BiasedLockingTest会设置为不可偏向所以新创建的对象是无锁状态 总结 批量重偏向和批量撤销是针对类的优化和对象无关。偏向锁重偏向一次之后不可再次重偏向。当某个类已经触发批量撤销机制后JVM会默认当前类产生了严重的问题剥夺了该类的新实例对象使用偏向锁的权利 锁升级的流程分析 }synchronized (lock){if(i17i21){log.debug(thread3-第 (i 1) 次加锁执行中\tClassLayout.parseInstance(lock).toPrintable());}}}},thread3).start();Thread.sleep(3000);log.debug(查看新创建的对象);log.debug((ClassLayout.parseInstance(new BiasedLockingTest()).toPrintable()));LockSupport.park(); }} 测试结果thread31-19 从无锁状态直接获取轻量级锁thread2释放锁之后变为无锁状态[外链图片转存中...(img-7XKUNu1b-1724205873397)]20-40 偏向锁撤销升级为轻量级锁thread2释放锁之后为偏向锁状态[外链图片转存中...(img-ukE0o4Gl-1724205873398)]达到偏向锁撤销的阈值40BiasedLockingTest会设置为不可偏向所以新创建的对象是无锁状态[外链图片转存中...(img-w6jSnilx-1724205873398)]#### 总结1. 批量重偏向和批量撤销是针对类的优化和对象无关。 2. 偏向锁重偏向一次之后不可再次重偏向。 3. 当某个类已经触发批量撤销机制后JVM会默认当前类产生了严重的问题剥夺了该类的新实例对象使用偏向锁的权利## 锁升级的流程分析[外链图片转存中...(img-WDkBpAXf-1724205873399)]
http://www.dnsts.com.cn/news/195455.html

相关文章:

  • 上海网站seoseodian高端网页开发平台
  • 知乎 上海做网站的公司天天向上 网站建设
  • 网站备案邮寄资料低价网站建设推广报价
  • 网站建设找哪一家比较好求个2022手机能看的
  • 百度网盘 做网站图床温州网上商城网站建设
  • 网站文件验证建设一个货架网站
  • 个人网站的首页wordpress怎么清空所有内容
  • 电子商务网站怎么建织梦 旅游网站模板
  • 西部数据网站管理助手v3.0网站推广流程
  • 如何加强省市级政门户网站建设电子商务网站建设与管理课程的意义
  • 安防网站模板下载八旬老太做直播 什么网站
  • .net网站开发文档北京城乡建设部网站首页北京
  • 看汽车图片的网站可以做壁纸百度商桥 手机网站
  • app网站建设哪家好网站建设中html模板
  • 开一个二手车销售网站怎么做域名查询服务器ip
  • 网站图标icowordpress评论时选填
  • 有没有做美食的视频网站做网站一般都是织梦
  • 长春市建设工程交易中心网站展厅设计规划
  • 做公司网站的多少钱优化设计五年级上册数学答案
  • 深圳微信网站公司哪家好公司建品牌网站好
  • 怎么在搜索引擎里做网站网页做问卷调查赚钱的网站
  • 的网站中国万网创始人
  • 乐山建设网站怎么建设ftp网站
  • 怎么看一个网站做没做竞价台州seo管理
  • 汕头市做网站全面的vi设计公司
  • 台州网站建设推广公司昆明网站制作定制公司
  • 怎样快速做网站北京大兴网站建设公司哪家好
  • 湛江有帮公司做网站网站优化三要素
  • 网站建设和商城有什么好处做网站是什么会计科目
  • 营销型公司和销售型公司泰安整站优化