默认情况下,Java 进程需要等待所有线程都运行结束,才会结束。有一种特殊的线程叫做守护线程,只要其它非守护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束。
package Daemon;import lombok.extern.slf4j.Slf4j;@Slf4j(topic = "c.demo1")public class demo1 { public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { while (true) { if (Thread.currentThread().isInterrupted()) { break; } } log.debug("结束"); }, "t1"); t1.setDaemon(true); t1.start(); Thread.sleep(1000); log.debug("结束"); }}输出:
15:08:26 [main] c.demo1 - 结束Process finished with exit code 0注意
这是从 操作系统 层面来描述的

这是从 Java API 层面来描述的

两个线程对初始值为 0 的静态变量一个做自增,一个做自减,各做 5000 次,结果是 0 吗?
package gc;import lombok.extern.slf4j.Slf4j;@Slf4j(topic = "c.demo1")public class demo1 { static int counter = 0; public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { for(int i=0;i<5000;i++){ counter++; } },"t1"); Thread t2 = new Thread(() -> { for(int i=0;i<5000;i++){ counter--; } },"t2"); t1.start(); t2.start(); t1.join(); t2.join(); log.debug("{}",counter); }}输出:
16:03:58 [main] c.demo1 - -1238问题分析
以上的结果可能是正数、负数、零。为什么呢?因为 Java 中对静态变量的自增,自减并不是原子操作,要彻底理解,必须从字节码来进行分析
例如对于 i++ 而言(i 为静态变量),实际会产生如下的 JVM 字节码指令:
getstatic i // 获取静态变量i的值iconst_1 // 准备常量1iadd // 自增putstatic i // 将修改后的值存入静态变量i而 Java 的内存模型如下,完成静态变量的自增,自减需要在主存和工作内存中进行数据交换:

多个线程在临界区内执行,由于代码的执行序列不同而导致结果无法预测,称之为发生了竞态条件
使用阻塞式的解决方案:synchronized,来解决上述问题,即俗称的【对象锁】,它采用互斥的方式让同一时刻至多只有一个线程能持有【对象锁】,其它线程再想获取这个【对象锁】时就会阻塞住。这样就能保证拥有锁的线程可以安全的执行临界区内的代码,不用担心线程上下文切换
注意
虽然 java 中互斥和同步都可以采用 synchronized 关键字来完成,但它们还是有区别的:
package gc;import lombok.extern.slf4j.Slf4j;@Slf4j(topic = "c.demo1")public class demo1 { static int counter = 0; static final Object lock = new Object(); public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { for(int i=0;i<5000;i++){ synchronized (lock){ counter++; } } },"t1"); Thread t2 = new Thread(() -> { for(int i=0;i<5000;i++){ synchronized (lock){ counter--; } } },"t2"); t1.start(); t2.start(); t1.join(); t2.join(); log.debug("{}",counter); }}输出:
16:14:15 [main] c.demo1 - 0改进:由面向过程改为面向对象
package gc;import lombok.extern.slf4j.Slf4j;@Slf4j(topic = "c.demo1")public class demo1 { public static void main(String[] args) throws InterruptedException { Room room = new Room(); Thread t1 = new Thread(() -> { for(int i=0;i<5000;i++){ room.increment(); } },"t1"); Thread t2 = new Thread(() -> { for(int i=0;i<5000;i++){ room.decrement(); } },"t2"); t1.start(); t2.start(); t1.join(); t2.join(); log.debug("{}",room.getCounter()); }}class Room{ private int counter = 0; public void increment(){ synchronized (this){ counter++; } } public void decrement(){ synchronized (this){ counter--; } } public int getCounter(){ synchronized (this){ return counter; } }}输出:
16:18:22 [main] c.demo1 - 0class Test{ public synchronized void test() { }}等价于class Test{ public void test() { synchronized(this) { } }}class Test{ public synchronized static void test() { }}等价于class Test{ public static void test() { synchronized(Test.class) { } }}public static void test1() { int i = 10; i++; }每个线程调用 test1() 方法时局部变量 i,会在每个线程的栈帧内存中被创建多份,因此不存在共享,所以局部变量是线程安全的
这里说它们是线程安全的是指,多个线程调用它们同一个实例的某个方法时,是线程安全的。也可以理解为
Hashtable table = new Hashtable();new Thread(()->{ table.put("key", "value1");}).start();new Thread(()->{ table.put("key", "value2");}).start();String、Integer 等都是不可变类,因为其内部的状态不可以改变,因此它们的方法都是线程安全的
(以 32 位虚拟机为例)
普通对象

Klass Word:是一个指针,指向该对象从属的 class 类
数组对象

其中 Mark Word 结构为:


过程:
首先用一个指针试图将 obj 对象与操作系统中的 Monitor 对象关联。在正常状态下,Mark Word 存储了 hashcode,age等信息,并且加锁状态码为 “01” 表示并未与任何锁关联。但是一旦获得了锁,加锁状态码会改为 “10” 并且抛弃掉存储的 hashcode ,age 等信息,转而存储一个指向 Monitor 对象的指针(ptr_to_heavyweight_monitor),占30位
此时线程 Thread-2 指向 Monitor 中的 Owner 表示自己是这把锁现在的主人
当一个新的线程到来时(Thread-1),会先去检查此对象有没有关联 Monitor 对象,发现已经关联,继而检查 Monitor 对象中的 Owner 已经是 Thread-2 了,此时 Thread-1会跟Monitor 中的 EntryList(阻塞队列) 关联,进入 BLOCK 状态

Thread-2 执行完同步代码块的内容,然后唤醒 EntryList 中等待的线程来竞争锁,竞争的时是非公平的
轻量级锁的使用场景:如果一个对象虽然有多线程要加锁,但加锁的时间是错开的(也就是没有竞争),那么可以使用轻量级锁来优化。
轻量级锁对使用者是透明的,即语法仍然是 synchronized
假设有两个方法同步块,利用同一个对象加锁
static final Object obj = new Object();public static void method1() { synchronized( obj ) { // 同步块 A method2(); }}public static void method2() { synchronized( obj ) { // 同步块 B }}创建锁记录(Lock Record)对象,每个线程都的栈帧都会包含一个锁记录的结构,内部可以存储锁定对象的 Mark Word

让锁记录中 Object reference 指向锁对象,并尝试用 cas 替换 Object 的 Mark Word,将 Mark Word 的值存入锁记录

如果 cas 替换成功,对象头中存储了 锁记录地址和状态 00 ,表示由该线程给对象加锁,这时图示如下

如果 cas 失败,有两种情况
如果是其它线程已经持有了该 Object 的轻量级锁,这时表明有竞争,进入锁膨胀过程
如果是自己执行了 synchronized 锁重入,那么再添加一条 Lock Record 作为重入的计数

当退出 synchronized 代码块(解锁时)如果有取值为 null 的锁记录,表示有重入,这时重置锁记录,表示重入计数减一

当退出 synchronized 代码块(解锁时)锁记录的值不为 null,这时使用 cas 将 Mark Word 的值恢复给对象头
如果在尝试加轻量级锁的过程中,CAS 操作无法成功,这时一种情况就是有其它线程为此对象加上了轻量级锁(有竞争),这时需要进行锁膨胀,将轻量级锁变为重量级锁。
static Object obj = new Object();public static void method1() { synchronized( obj ) { // 同步块 }}当 Thread-1 进行轻量级加锁时,Thread-0 已经对该对象加了轻量级锁

这时 Thread-1 加轻量级锁失败,进入锁膨胀流程
即为 Object 对象申请 Monitor 锁,让 Object 指向重量级锁地址
然后自己进入 Monitor 的 EntryList BLOCKED

当 Thread-0 退出同步块解锁时,使用 cas 将 Mark Word 的值恢复给对象头,失败。这时会进入重量级解锁流程,即按照 Monitor 地址找到 Monitor 对象,设置 Owner 为 null,唤醒 EntryList 中 BLOCKED 线程
重量级锁竞争的时候,还可以使用自旋来进行优化,如果当前线程自旋成功(即这时候持锁线程已经退出了同步块,释放了锁),这时当前线程就可以避免阻塞。
轻量级锁在没有竞争时(就自己这个线程),每次重入仍然需要执行 CAS 操作。
Java 6 中引入了偏向锁来做进一步优化:只有第一次使用 CAS 将线程 ID 设置到对象的 Mark Word 头,之后发现这个线程 ID 是自己的就表示没有竞争,不用重新 CAS。以后只要不发生竞争,这个对象就归该线程所有
例如:
static final Object obj = new Object();public static void m1() { synchronized( obj ) { // 同步块 A m2(); }}public static void m2() { synchronized( obj ) { // 同步块 B m3(); }}public static void m3() { synchronized( obj ) { // 同步块 C }}
一个对象创建时:
1) 测试延迟特性
2) 测试偏向锁
class Dog {}利用 jol 第三方工具来查看对象头信息
// 添加虚拟机参数 -XX:BiasedLockingStartupDelay=0 public static void main(String[] args) throws IOException { Dog d = new Dog(); ClassLayout classLayout = ClassLayout.parseInstance(d); new Thread(() -> { log.debug("synchronized 前"); System.out.println(classLayout.toPrintableSimple(true)); synchronized (d) { log.debug("synchronized 中"); System.out.println(classLayout.toPrintableSimple(true)); } log.debug("synchronized 后"); System.out.println(classLayout.toPrintableSimple(true)); }, "t1").start();}输出:
11:08:58.117 c.TestBiased [t1] - synchronized 前00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000101 11:08:58.121 c.TestBiased [t1] - synchronized 中00000000 00000000 00000000 00000000 00011111 11101011 11010000 00000101 11:08:58.121 c.TestBiased [t1] - synchronized 后00000000 00000000 00000000 00000000 00011111 11101011 11010000 000001013)测试禁用
在上面测试代码运行时在添加 VM 参数 -XX:-UseBiasedLocking 禁用偏向锁
输出:
11:13:10.018 c.TestBiased [t1] - synchronized 前00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 11:13:10.021 c.TestBiased [t1] - synchronized 中00000000 00000000 00000000 00000000 00100000 00010100 11110011 10001000 11:13:10.021 c.TestBiased [t1] - synchronized 后00000000 00000000 00000000 00000000 00000000 00000000 00000000 000000014)测试 hashCode
正常状态对象一开始是没有 hashCode 的,第一次调用才生成
调用了对象的 hashCode,但偏向锁的对象 MarkWord 中存储的是线程 id,如果调用 hashCode 会导致偏向锁被撤销
在调用 hashCode 后使用偏向锁,记得去掉 -XX:-UseBiasedLocking
输出:
11:22:10.386 c.TestBiased [main] - 调用 hashCode:1778535015 11:22:10.391 c.TestBiased [t1] - synchronized 前00000000 00000000 00000000 01101010 00000010 01001010 01100111 00000001 11:22:10.393 c.TestBiased [t1] - synchronized 中00000000 00000000 00000000 00000000 00100000 11000011 11110011 01101000 11:22:10.393 c.TestBiased [t1] - synchronized 后00000000 00000000 00000000 01101010 00000010 01001010 01100111 00000001当有其它线程使用偏向锁对象时,会将偏向锁升级为轻量级锁
private static void test2() throws InterruptedException { Dog d = new Dog(); Thread t1 = new Thread(() -> { synchronized (d) { log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true)); } synchronized (TestBiased.class) { TestBiased.class.notify(); } // 如果不用 wait/notify 使用 join 必须打开下面的注释 // 因为:t1 线程不能结束,否则底层线程可能被 jvm 重用作为 t2 线程,底层线程 id 是一样的 /*try { System.in.read(); } catch (IOException e) { e.printStackTrace(); }*/ }, "t1"); t1.start(); Thread t2 = new Thread(() -> { synchronized (TestBiased.class) { try { TestBiased.class.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true)); synchronized (d) { log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true)); } log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true)); }, "t2"); t2.start(); }输出:
[t1] - 00000000 00000000 00000000 00000000 00011111 01000001 00010000 00000101 [t2] - 00000000 00000000 00000000 00000000 00011111 01000001 00010000 00000101 [t2] - 00000000 00000000 00000000 00000000 00011111 10110101 11110000 01000000 [t2] - 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001public static void main(String[] args) throws InterruptedException { Dog d = new Dog(); Thread t1 = new Thread(() -> { log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true)); synchronized (d) { log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true)); try { d.wait(); } catch (InterruptedException e) { e.printStackTrace(); } log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true)); } }, "t1"); t1.start(); new Thread(() -> { try { Thread.sleep(6000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (d) { log.debug("notify"); d.notify(); } }, "t2").start(); }输出:
[t1] - 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000101 [t1] - 00000000 00000000 00000000 00000000 00011111 10110011 11111000 00000101 [t2] - notify [t1] - 00000000 00000000 00000000 00000000 00011100 11010100 00001101 11001010如果对象虽然被多个线程访问,但没有竞争,这时偏向了线程 T1 的对象仍有机会重新偏向 T2,重偏向会重置对象的 Thread ID
当撤销偏向锁阈值超过 20 次后,jvm 会这样觉得,我是不是偏向错了呢,于是会在给这些对象加锁时重新偏向至加锁线程
private static void test3() throws InterruptedException { Vector<Dog> list = new Vector<>(); Thread t1 = new Thread(() -> { for (int i = 0; i < 30; i++) { Dog d = new Dog(); list.add(d); synchronized (d) { log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true)); } } synchronized (list) { list.notify(); } }, "t1"); t1.start(); Thread t2 = new Thread(() -> { synchronized (list) { try { list.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } log.debug("===============> "); for (int i = 0; i < 30; i++) { Dog d = list.get(i); log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true)); synchronized (d) { log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true)); } log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true)); } }, "t2"); t2.start(); }输出:
[t1] - 0 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 [t1] - 1 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 [t1] - 2 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 [t1] - 3 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 [t1] - 4 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 [t1] - 5 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 [t1] - 6 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 [t1] - 7 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 [t1] - 8 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 [t1] - 9 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 [t1] - 10 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 [t1] - 11 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 [t1] - 12 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 [t1] - 13 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 [t1] - 14 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 [t1] - 15 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 [t1] - 16 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 [t1] - 17 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 [t1] - 18 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 [t1] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 [t1] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 [t1] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 [t1] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 [t1] - 23 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 [t1] - 24 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 [t1] - 25 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 [t1] - 26 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 [t1] - 27 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 [t1] - 28 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 [t1] - 29 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 [t2] - ===============> [t2] - 0 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 [t2] - 0 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 [t2] - 0 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 [t2] - 1 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 [t2] - 1 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 [t2] - 1 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 [t2] - 2 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 [t2] - 2 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 [t2] - 2 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 [t2] - 3 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 [t2] - 3 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 [t2] - 3 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 [t2] - 4 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 [t2] - 4 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 [t2] - 4 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 [t2] - 5 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 [t2] - 5 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 [t2] - 5 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 [t2] - 6 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 [t2] - 6 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 [t2] - 6 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 [t2] - 7 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101[t2] - 7 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 [t2] - 7 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 [t2] - 8 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 [t2] - 8 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 [t2] - 8 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 [t2] - 9 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 [t2] - 9 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 [t2] - 9 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 [t2] - 10 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 [t2] - 10 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 [t2] - 10 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 [t2] - 11 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 [t2] - 11 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 [t2] - 11 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 [t2] - 12 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 [t2] - 12 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 [t2] - 12 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 [t2] - 13 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 [t2] - 13 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 [t2] - 13 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 [t2] - 14 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 [t2] - 14 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 [t2] - 14 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 [t2] - 15 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 [t2] - 15 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 [t2] - 15 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 [t2] - 16 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 [t2] - 16 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 [t2] - 16 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 [t2] - 17 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 [t2] - 17 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 [t2] - 17 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 [t2] - 18 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 [t2] - 18 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 [t2] - 18 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 [t2] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 [t2] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 [t2] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 [t2] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 [t2] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 [t2] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 [t2] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 [t2] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 [t2] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 [t2] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 [t2] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 [t2] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 [t2] - 23 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 [t2] - 23 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 [t2] - 23 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 [t2] - 24 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 [t2] - 24 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 [t2] - 24 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101[t2] - 25 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 [t2] - 25 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 [t2] - 25 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 [t2] - 26 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 [t2] - 26 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 [t2] - 26 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 [t2] - 27 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 [t2] - 27 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 [t2] - 27 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 [t2] - 28 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 [t2] - 28 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 [t2] - 28 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 [t2] - 29 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 [t2] - 29 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 [t2] - 29 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101当撤销偏向锁阈值超过 40 次后,jvm 会这样觉得,自己确实偏向错了,根本就不该偏向。于是整个类的所有对象都会变为不可偏向的,新建的对象也是不可偏向的
static Thread t1,t2,t3; private static void test4() throws InterruptedException { Vector<Dog> list = new Vector<>(); int loopNumber = 39; t1 = new Thread(() -> { for (int i = 0; i < loopNumber; i++) { Dog d = new Dog(); list.add(d); synchronized (d) { log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true)); } } LockSupport.unpark(t2); }, "t1"); t1.start(); t2 = new Thread(() -> { LockSupport.park(); log.debug("===============> "); for (int i = 0; i < loopNumber; i++) { Dog d = list.get(i); log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true)); synchronized (d) { log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true)); } log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true)); } LockSupport.unpark(t3); }, "t2"); t2.start(); t3 = new Thread(() -> { LockSupport.park(); log.debug("===============> "); for (int i = 0; i < loopNumber; i++) { Dog d = list.get(i); log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true)); synchronized (d) { log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true)); } log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true)); } }, "t3"); t3.start(); t3.join(); log.debug(ClassLayout.parseInstance(new Dog()).toPrintableSimple(true)); }