• 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));
        }
    
  • 相关阅读:
    如何模拟一个小程序项目打包的流程
    android studio导入eclipse项目
    Git 作用&&操作
    ZYNQ从放弃到入门(十二)- AMP — Zynq 上的非对称多核处理器
    运营商大数据,三网融合大数据,联通大数据,移动大数据
    C++ 构造函数
    6月B站和微博达人涨粉榜单,微博涨粉榜一竟是TA
    rk3588 usb网络共享连接
    服务器遭遇挖矿病毒syst3md及其伪装者rcu-sched:原因、症状与解决方案
    php 正则匹配中文汉字
  • 原文地址:https://www.cnblogs.com/lcha-coding/p/16349444.html