Java 多线程共享模型之管程(上)

主线程与守护线程

默认情况下,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 

注意

  • 垃圾回收器线程就是一种守护线程
  • Tomcat 中的 Acceptor 和 Poller 线程都是守护线程,所以 Tomcat 接收到 shutdown 命令后,不会等待它们处理完当前请求

五种状态

这是从 操作系统 层面来描述的

  • 【初始状态】仅是在语言层面创建了线程对象,还未与操作系统线程关联
  • 【可运行状态】(就绪状态)指该线程已经被创建(与操作系统线程关联),可以由 CPU 调度执行
  • 【运行状态】指获取了 CPU 时间片运行中的状态
    • 当 CPU 时间片用完,会从【运行状态】转换至【可运行状态】,会导致线程的上下文切换
  • 【阻塞状态】
    • 如果调用了阻塞 API,如 BIO 读写文件,这时该线程实际不会用到 CPU,会导致线程上下文切换,进入【阻塞状态】
    • 等 BIO 操作完毕,会由操作系统唤醒阻塞的线程,转换至【可运行状态】
    • 与【可运行状态】的区别是,对【阻塞状态】的线程来说只要它们一直不唤醒,调度器就一直不会考虑调度它们
  • 【终止状态】表示线程已经执行完毕,生命周期已经结束,不会再转换为其它状态

六种状态

这是从 Java API 层面来描述的

  • NEW 线程刚被创建,但是还没有调用 start() 方法
  • RUNNABLE 当调用了 start() 方法之后,注意,Java API 层面的 RUNNABLE 状态涵盖了 操作系统 层面的【可运行状态】、【运行状态】和【阻塞状态】(由于 BIO 导致的线程阻塞,在 Java 里无法区分,仍然认为是可运行)
  • BLOCKED , WAITING , TIMED_WAITING 都是 Java API 层面对【阻塞状态】的细分
  • TERMINATED 当线程代码运行结束

共享模型之管程

共享带来的问题

两个线程对初始值为 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 // 准备常量1 iadd // 自增 putstatic i // 将修改后的值存入静态变量i 

而 Java 的内存模型如下,完成静态变量的自增,自减需要在主存和工作内存中进行数据交换:

临界区

  • 一个程序运行多个线程本身是没有问题的
  • 问题出在多个线程访问共享资源
    • 多个线程读共享资源其实也没有问题
    • 在多个线程对共享资源读写操作时发生指令交错,就会出现问题
  • 一段代码块内如果存在对共享资源的多线程读写操作,称这段代码块为临界区

竞态条件

多个线程在临界区内执行,由于代码的执行序列不同而导致结果无法预测,称之为发生了竞态条件

synchronized 解决方案

使用阻塞式的解决方案: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 - 0 

方法上的 synchronized

  • 加在成员方法上,锁住的是 this
  • 加在静态方法上,锁住的是 类名.class
class 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,会在每个线程的栈帧内存中被创建多份,因此不存在共享,所以局部变量是线程安全的

常见的线程安全类

  • String
  • Integer
  • StringBuffffer
  • Random
  • Vector
  • Hashtable
  • java.util.concurrent 包下的类

这里说它们是线程安全的是指,多个线程调用它们同一个实例的某个方法时,是线程安全的。也可以理解为

Hashtable table = new Hashtable();  new Thread(()->{   table.put("key", "value1"); }).start();  new Thread(()->{   table.put("key", "value2"); }).start(); 
  • 它们的每个方法是原子的
  • 注意它们多个方法的组合不是原子的

不可变类线程安全性

String、Integer 等都是不可变类,因为其内部的状态不可以改变,因此它们的方法都是线程安全的

Monitor

Java 对象头

(以 32 位虚拟机为例)

  • 普通对象

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

  • 数组对象

其中 Mark Word 结构为:

  • hashcode:每个对象的哈希码
  • age:垃圾回收时用到的分代年龄
  • biased_lock:是否是偏向锁
  • (01,00,10,11):表示加锁状态

Monitor(锁)

过程:

  1. 首先用一个指针试图将 obj 对象与操作系统中的 Monitor 对象关联。在正常状态下,Mark Word 存储了 hashcode,age等信息,并且加锁状态码为 “01” 表示并未与任何锁关联。但是一旦获得了锁,加锁状态码会改为 “10” 并且抛弃掉存储的 hashcode ,age 等信息,转而存储一个指向 Monitor 对象的指针(ptr_to_heavyweight_monitor),占30位

  2. 此时线程 Thread-2 指向 Monitor 中的 Owner 表示自己是这把锁现在的主人

  3. 当一个新的线程到来时(Thread-1),会先去检查此对象有没有关联 Monitor 对象,发现已经关联,继而检查 Monitor 对象中的 Owner 已经是 Thread-2 了,此时 Thread-1会跟Monitor 中的 EntryList(阻塞队列) 关联,进入 BLOCK 状态

  4. Thread-2 执行完同步代码块的内容,然后唤醒 EntryList 中等待的线程来竞争锁,竞争的时是非公平的

synchronized 优化原理

轻量级锁

轻量级锁的使用场景:如果一个对象虽然有多线程要加锁,但加锁的时间是错开的(也就是没有竞争),那么可以使用轻量级锁来优化。

轻量级锁对使用者是透明的,即语法仍然是 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 线程

自旋优化

重量级锁竞争的时候,还可以使用自旋来进行优化,如果当前线程自旋成功(即这时候持锁线程已经退出了同步块,释放了锁),这时当前线程就可以避免阻塞。

  • 自旋会占用 CPU 时间,单核 CPU 自旋就是浪费,多核 CPU 自旋才能发挥优势。
  • 在 Java 6 之后自旋锁是自适应的,比如对象刚刚的一次自旋操作成功过,那么认为这次自旋成功的可能性会高,就多自旋几次;反之,就少自旋甚至不自旋,总之,比较智能。
  • Java 7 之后不能控制是否开启自旋功能

偏向锁

轻量级锁在没有竞争时(就自己这个线程),每次重入仍然需要执行 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   } } 
偏向状态

一个对象创建时:

  • 如果开启了偏向锁(默认开启),那么对象创建后,markword 值为 0x05 即最后 3 位为 101,这时它的thread、epoch、age 都为 0
  • 偏向锁是默认是延迟的,不会在程序启动时立即生效,如果想避免延迟,可以加 VM 参数 -XX:BiasedLockingStartupDelay=0 来禁用延迟
  • 如果没有开启偏向锁,那么对象创建后,markword 值为 0x01 即最后 3 位为 001,这时它的 hashcode、age 都为 0,第一次用到 hashcode 时才会赋值

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 00000101 

3)测试禁用

在上面测试代码运行时在添加 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 00000001 

4)测试 hashCode

正常状态对象一开始是没有 hashCode 的,第一次调用才生成

撤销 – 调用对象 hashCode

调用了对象的 hashCode,但偏向锁的对象 MarkWord 中存储的是线程 id,如果调用 hashCode 会导致偏向锁被撤销

  • 轻量级锁会在锁记录中记录 hashCode
  • 重量级锁会在 Monitor 中记录 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 00000001 
撤销 – 调用 wait / notify
public 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));     } 

商匡云商
Logo
对比商品
  • 合计 (0)
对比
0
购物车