[03][01][02] 多线程的基本原理以及挑战

1. 一个问题引发的思考

1.1 线程安全性

当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些进程将如何交替执行,并且在主调代码中不需要任何额外的同步或协调,这个类都能表现出正确的行为,那么就称这个类是线程安全的

线程安全性主要体现:

  • 原子性:提供了互斥访问,同一时刻只能有一个线程来对它进行操作
  • 可见性:一个线程对主内存的修改可以及时的被其他线程观察到
  • 有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序的存在,该观察结果一般是杂乱无序

1.1.1 原子性

10000个线程对count做自增计算,最后输出结果并不一定是10000,不能达到预期结果10000,自增计算不符合原子性

package com.zhunongyun.toalibaba.distributed.concurrent.programming.basicprinciples;

public class AtomicDemo {

    private static Long count = 0L;

    public static void main(String[] args) throws InterruptedException{

        for (int i = 0; i < 10000; i++) {
            new Thread(() -> {
                count++;
            }).start();
        }
        Thread.sleep(5000);
        System.out.println("运行结果: " + count);
    }
}

执行结果存在多种可能

运行结果: 9998

2. synchronized的基本认识

  • Synchronized可以保证在同一时刻,只有一个线程可以执行某一个方法或者代码块
  • 同步的作用不仅仅是互斥,它的另一个作用就是共享可变性,当某个线程修改了可变数据并释放锁后,其它线程可以获取被修改变量的最新值,如果没有正确的同步,这种修改对其它线程是不可见的

2.1 Synchronized的基本使用

Synchronized是Java中解决并发问题的一种最常用的方法,也是最简单的一种方法,Synchronized的作用主要有三个

  • 确保线程互斥的访问同步代码
  • 保证共享变量的修改能够及时可见
  • 有效解决重排序问题

从语法上讲,Synchronized总共有四种不同的同步块

  • 同步的实例方法(锁用的是其实例对象本身,所有的非静态同步方法执行需要顺序执行,即不能并行执行)
  • 同步的静态方法(锁用的是其类对象本身,所有的静态同步方法执行需要顺序执行,即不能并行执行)
  • 实例方法中的同步块(锁是自己指定的,但不能是引用性对象及null对象)
  • 静态方法中的同步块(锁是自己指定的,但不能是引用性对象及null对象)

2.1.1 静态同步方法与非静态同步方法区别

非静态同步方法用的都是同一把锁——实例对象本身,也就是说如果一个实例对象的非静态同步方法获取锁后,该实例对象的其他非静态同步方法必须等待获取锁的方法释放锁后才能获取锁,可是别的实例对象的非静态同步方法因为跟该实例对象的非静态同步方法用的是不同的锁,所以毋须等待该实例对象已获取锁的非静态同步方法释放锁就可以获取他们自己的锁

静态同步方法用的也是同一把锁——类对象本身,这两把锁是两个不同的对象,所以静态同步方法与非静态同步方法之间是不会有竞态条件的,但是一旦一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁,而不管是同一个实例对象的静态同步方法之间,还是不同的实例对象的静态同步方法之间,只要它们同一个类的实例对象!

2.1.2 同步块

同步块,由于其锁是可以选择的,所以只有使用同一把锁的同步块之间才有着竞态条件,这就得具体情况具体分析了,但这里有个需要注意的地方,同步块的锁是可以选择的,但是不是可以任意选择的

这里必须要注意一个物理对象和一个引用对象的实例变量之间的区别!使用一个引用对象的实例变量作为锁并不是一个好的选择,因为同步块在执行过程中可能会改变它的值,其中就包括将其设置为null,而对一个null对象加锁会产生异常,并且对不同的对象加锁也违背了同步的初衷!这看起来是很清楚的,但是一个经常发生的错误就是选用了错误的锁对象

因此必须注意:同步是基于实际对象而不是对象引用的!多个变量可以引用同一个对象,变量也可以改变其值从而指向其他的对象,因此,当选择一个对象锁时,我们要根据实际对象而不是其引用来考虑!作为一个原则,不要选择一个可能会在锁的作用域中改变值的实例变量作为锁对象

2.1.3 Java中多线程锁释放的条件

  • 执行完同步代码块,就会释放锁(synchronized)
  • 在执行同步代码块的过程中,遇到异常而导致线程终止,锁也会被释放(exception)
  • 在执行同步代码块的过程中,执行了锁所属对象的wait()方法,这个线程会释放锁,进入对象的等待池(wait)

从上面的三点我就可以看到stop方法释放锁是在第二点的,通过抛出异常来释放锁,通过证明,这种方式是不安全的,不可靠的

2.2 代码实现

2.2.1 没有同步的场景

package com.zhunongyun.toalibaba.distributed.concurrent.programming.basicprinciples;

/**
 * 没有同步的场景
 */
public class SynchronizedTest {
    public void method1() {
        System.out.println(Thread.currentThread().getName() + " 方法 method1 开始执行");
        try {
            System.out.println(Thread.currentThread().getName() + " 方法 method1 正在执行");
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "  方法 method1 结束执行");
    }

    public void method2() {
        System.out.println(Thread.currentThread().getName() + " 方法 method2 开始执行");
        try {
            System.out.println(Thread.currentThread().getName() + " 方法 method2 正在执行");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "  方法 method2 结束执行");
    }

    public static void main(String[] args) {
        final SynchronizedTest synchronizedTest = new SynchronizedTest();

        new Thread(() -> {
            synchronizedTest.method1();
        }, "thread1").start();

        new Thread(() -> {
            synchronizedTest.method2();
        }, "thread2").start();
    }
}

执行结果

线程1和线程2同时进入执行状态,线程1在执行过程中sleep 3秒,线程2执行速度比线程1快,所以线程2先执行完成,这个过程中线程1和线程2是同时执行的

thread1 方法 method1 开始执行
thread1 方法 method1 正在执行
thread2 方法 method2 开始执行
thread2 方法 method2 正在执行
thread2  方法 method2 结束执行
thread1  方法 method1 结束执行

2.2.2 普通方法同步

package com.zhunongyun.toalibaba.distributed.concurrent.programming.basicprinciples;

/**
 * 普通方法同步
 */
public class SynchronizedTest2 {
    public synchronized void method1() {
        System.out.println(Thread.currentThread().getName() + " 方法 method1 开始执行");
        try {
            System.out.println(Thread.currentThread().getName() + " 方法 method1 正在执行");
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " 方法 method1 结束执行");
    }

    public synchronized void method2() {
        System.out.println(Thread.currentThread().getName() + " 方法 method2 开始执行");
        try {
            System.out.println(Thread.currentThread().getName() + " 方法 method2 正在执行");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " 方法 method2 结束执行");
    }

    public void method3() {
        System.out.println(Thread.currentThread().getName() + " 方法 method3 开始执行");
        try {
            System.out.println(Thread.currentThread().getName() + " 方法 method3 正在执行");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " 方法 method3 结束执行");
    }

    public static void main(String[] args) {
//        test2demo1();
//        test2demo2();
        test2demo3();
    }

    private static void test2demo1() {
        final SynchronizedTest2 test = new SynchronizedTest2();

        new Thread(() -> {
            test.method1();
        }, "thread1").start();

        new Thread(() -> {
            test.method2();
        }, "thread2").start();
    }

    private static void test2demo2() {
        final SynchronizedTest2 test = new SynchronizedTest2();
        final SynchronizedTest2 test2 = new SynchronizedTest2();

        new Thread(() -> {
            test.method1();
        }, "thread1").start();

        new Thread(() -> {
            test2.method2();
        }, "thread2").start();
    }

    private static void test2demo3() {
        final SynchronizedTest2 test = new SynchronizedTest2();

        new Thread(() -> {
            test.method1();
        }, "thread1").start();

        new Thread(() -> {
            test.method3();
        }, "thread2").start();
    }
}

依次执行test2demo1(),test2demo2(),test2demo3()方法

  • test2demo2测试方法中说明:test和test2属于不同的对象,所以同步互不干扰,线程1和线程2会并行执行
  • 通过test2demo1与test2demo3对比说明:同一个类中的所有synchronized修饰的方法是不能同时调用的,也就是说同时只能调用其中一个方法,比如线程1调用method1方法,在method1方法执行完之前,线程2调用method2方法,这个时候线程2就会阻塞,直到线程1调用完method1方法后,线程2才开始执行method2方法(不仅仅是多个线程调用同一个同步方法)
  • 如果线程拥有同步和非同步方法,则非同步方法可以被多个线程自由访问而不受锁的限制

test2demo1的执行结果

thread1 方法 method1 开始执行
thread1 方法 method1 正在执行
thread1 方法 method1 结束执行
thread2 方法 method2 开始执行
thread2 方法 method2 正在执行
thread2 方法 method2 结束执行

test2demo2的执行结果

thread1 方法 method1 开始执行
thread1 方法 method1 正在执行
thread2 方法 method2 开始执行
thread2 方法 method2 正在执行
thread2 方法 method2 结束执行
thread1 方法 method1 结束执行

test2demo3的执行结果

thread1 方法 method1 开始执行
thread1 方法 method1 正在执行
thread2 方法 method3 开始执行
thread2 方法 method3 正在执行
thread2 方法 method3 结束执行
thread1 方法 method1 结束执行

2.2.3 静态方法(类)同步

package com.zhunongyun.toalibaba.distributed.concurrent.programming.basicprinciples;

/**
 * 静态方法(类)同步
 */
public class SynchronizedTest3 {
    public static synchronized void method1() {
        System.out.println(Thread.currentThread().getName() + " 方法 method1 开始执行");
        try {
            System.out.println(Thread.currentThread().getName() + " 方法 method1 正在执行");
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " 方法 method1 结束执行");
    }

    public static synchronized void method2() {
        System.out.println(Thread.currentThread().getName() + " 方法 method2 开始执行");
        try {
            System.out.println(Thread.currentThread().getName() + " 方法 method2 正在执行");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " 方法 method2 结束执行");
    }

    public synchronized void method3() {
        System.out.println(Thread.currentThread().getName() + " 方法 method3 开始执行");
        try {
            System.out.println(Thread.currentThread().getName() + " 方法 method3 正在执行");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " 方法 method3 结束执行");
    }

    public static void main(String[] args) {
        test3demo1();
//        test3demo2();
//        test3demo3();
    }

    private static void test3demo1() {
        final SynchronizedTest3 test = new SynchronizedTest3();

        new Thread(() -> {
            test.method1();
        }, "thread1").start();

        new Thread(() -> {
            test.method2();
        }, "thread2").start();
    }

    private static void test3demo2() {
        final SynchronizedTest3 test = new SynchronizedTest3();
        final SynchronizedTest3 test2 = new SynchronizedTest3();

        new Thread(() -> {
            test.method1();
        }, "thread1").start();

        new Thread(() -> {
            test2.method2();
        }, "thread2").start();
    }

    private static void test3demo3() {
        final SynchronizedTest3 test = new SynchronizedTest3();

        new Thread(() -> {
            test.method1();
        }, "thread1").start();

        new Thread(() -> {
            test.method3();
        }, "thread2").start();
    }
}

依次执行test3demo1(),test3demo2(),test3demo3()方法

  • test3demo1与test3demo2对比:对静态方法的同步本质上是对类的同步(静态方法本质上是属于类的方法,而不是对象上的方法),所以即使test和test2属于不同的对象,但是它们都属于SynchronizedTest3类的实例,所以也只能顺序的执行method1和method2,不能并发执行
  • test3demo1与test3demo3对比说明:实例对象上的锁与类对象的锁是两个,所以可以并行的执行method1和method3

test3demo1执行结果

thread1 方法 method1 开始执行
thread1 方法 method1 正在执行
thread1 方法 method1 结束执行
thread2 方法 method2 开始执行
thread2 方法 method2 正在执行
thread2 方法 method2 结束执行

test3demo2执行结果

thread1 方法 method1 开始执行
thread1 方法 method1 正在执行
thread1 方法 method1 结束执行
thread2 方法 method2 开始执行
thread2 方法 method2 正在执行
thread2 方法 method2 结束执行

test3demo3执行结果

thread1 方法 method1 开始执行
thread1 方法 method1 正在执行
thread2 方法 method3 开始执行
thread2 方法 method3 正在执行
thread2 方法 method3 结束执行
thread1 方法 method1 结束执行

2.2.4 代码块同步

package com.zhunongyun.toalibaba.distributed.concurrent.programming.basicprinciples;

/**
 * 代码块同步
 */
public class SynchronizedTest4 {
    public void method1() {
        System.out.println(Thread.currentThread().getName() + " 方法 method1 开始执行");
        try {
            synchronized (this) {
                System.out.println(Thread.currentThread().getName() + " 方法 method1 正在执行");
                Thread.sleep(3000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " 方法 method1 结束执行");
    }

    public void method2() {
        System.out.println(Thread.currentThread().getName() + " 方法 method2 开始执行");
        try {
            synchronized (this) {
                System.out.println(Thread.currentThread().getName() + " 方法 method2 正在执行");
                Thread.sleep(1000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " 方法 method2 结束执行");
    }

    public static void main(String[] args) {
        test4demo1();
//        test4demo2();
    }

    private static void test4demo1() {
        final SynchronizedTest4 test = new SynchronizedTest4();

        new Thread(() -> {
            test.method1();
        }, "thread1").start();

        new Thread(() -> {
            test.method2();
        }, "thread2").start();
    }

    private static void test4demo2() {
        final SynchronizedTest4 test = new SynchronizedTest4();
        final SynchronizedTest4 test2 = new SynchronizedTest4();

        new Thread(() -> {
            test.method1();
        }, "thread1").start();

        new Thread(() -> {
            test2.method2();
        }, "thread2").start();
    }
}

依次执行test4demo1(),test4demo2()方法

  • 虽然线程1和线程2都进入了对应的方法开始执行,但是线程2在进入同步块之前,需要等待线程1中同步块执行完成

test4demo1执行结果

thread1 方法 method1 开始执行
thread1 方法 method1 正在执行
thread2 方法 method2 开始执行
thread1 方法 method1 结束执行
thread2 方法 method2 正在执行
thread2 方法 method2 结束执行

test4demo2执行结果

thread1 方法 method1 开始执行
thread1 方法 method1 正在执行
thread2 方法 method2 开始执行
thread2 方法 method2 正在执行
thread2 方法 method2 结束执行
thread1 方法 method1 结束执行

3. 思考锁的存储

代码地址:http://hg.openjdk.java.net/jdk8u/jdk8u40/hotspot/file/68577993c7db/src/share/vm/oops/markOop.hpp

单词解释

  • hash:保存对象的哈希码
  • age:保存对象的分代年龄
  • biased_lock:偏向锁标识位
  • lock:锁状态标识位
  • JavaThread*:保存持有偏向锁的线程ID
  • epoch:保存偏向时间戳

上图中有源码中对锁标志位这样枚举:

enum {
    locked_value             = 0,//00 轻量级锁
    unlocked_value           = 1,//01 无锁
    monitor_value            = 2,//10 监视器锁,也叫膨胀锁,也叫重量级锁
    marked_value             = 3,//11 GC标记
    biased_lock_pattern      = 5 //101 偏向锁
};

在hotspot中找到markOop.hpp文件,注释中存在这样一段描述

//  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)

对象在虚拟机中的结构是怎样的,来看看Hotsopt中对象在内存中的结构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3EBcWWXA-1570264186284)(evernotecid://DE37D598-33A4-4708-800A-6D4CF841DDE2/wwwevernotecom/149352153/ENResource/p297)]

在上面这张图里面可以看出,对象在内存中的机构主要包含以下几个部分

  • MarkWord:对象的MarkWord部分占4个字节,其内容是一系列的标记位,比如轻量级的标记位,偏向锁标记位等等
  • Class对象指针:Class对象指针的大小也是4个字节,其指向的位置是对象对应的Class对象(其对应的元数据对象)的内存地址
  • 对象实际数据:这里面包括了对象的所有成员变量,其大小由各个成员变量的大小决定,比如:byte和boolean是1个字节,short和char是2个字节,int和float是4个字节,long和double是8个字节,refrence是4个字节
  • 对齐:最后一部分是对齐填充的字节,按8个字节填充

由于目前基本都在使用64位JVM,此处不再对32位的结构进行详细说明

偏向锁标识位 锁标识位 锁状态 存储内容
0 01 未锁定 hashcode(31),年龄(4)
1 01 偏向锁 线程ID(54),时间戳(2),年龄(4)
00 轻量级锁 栈中锁记录的指针(64)
10 重量级锁 monitor的指针(64)
11 GC标记 空,不需要记录信息

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qgwXxRiE-1570264186287)(evernotecid://DE37D598-33A4-4708-800A-6D4CF841DDE2/wwwevernotecom/149352153/ENResource/p298)]

4. synchronized锁的升级原理

synchronized提供的集中锁机制,依次升级为无锁-->偏向锁-->轻量级锁-->重量级锁

在jdk1.6之前,synchronized是基于重量级锁来实现的对象锁,重量级锁依赖于操作系统的互斥量(mutex)实现,该操作会导致进程从用户态与内核态之间的切换,是一个开销较大的操作

synchronized的锁粒度控制和无锁化进行优化,加入偏向锁和轻量级锁,实现既能保证数据安全,也能保证性能

对于一个synchronized修饰的同步代码块,假设有线程A,线程B,多个线程三种场景访问

  • 只有线程A访问–>引入偏向级锁(hotspot研究发现大部分情况是属于这种)
  • 线程A和线程B交替访问–>轻量级锁–>自旋(偏向级锁指向线程A,线程B想要拿到锁)
  • 多线程同时访问–>重量级锁

4.1 cas操作

CAS:Compare and Swap,为了提高性能,JVM很多操作都依赖CAS实现,一种乐观锁的实现.

JNI来完成CPU指令的操作:

unsafe.compareAndSwapInt(this,valueOffset,expect,update);

CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B.如果A=V,那么把B赋值给V,返回V;如果A!=V,直接返回V.

打开源码:openjdk\hotspot\src\oscpu\windowsx86\vm\atomicwindowsx86.inline.hpp,如下图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b6LV9dXm-1570264186288)(evernotecid://DE37D598-33A4-4708-800A-6D4CF841DDE2/wwwevernotecom/149352153/ENResource/p349)]

os::is_MP()这个是runtime/os.hpp,实际就是返回是否多处理器,源码如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AjqhD2LL-1570264186288)(evernotecid://DE37D598-33A4-4708-800A-6D4CF841DDE2/wwwevernotecom/149352153/ENResource/p350)]

如上面源代码所示(看第一个int参数即可),LOCK_IF_MP:会根据当前处理器的类型来决定是否为cmpxchg指令添加lock前缀.如果程序是在多处理器上运行,就为cmpxchg指令加上lock前缀(lockcmpxchg).反之,如果程序是在单处理器上运行,就省略lock前缀(单处理器自身会维护单处理器内的顺序一致性,不需要lock前缀提供的内存屏障效果).

4.2 偏向锁

按照之前的HotSpot设计,每次加锁/解锁都会涉及到一些CAS操作(比如对等待队列的CAS操作),CAS操作会延迟本地调用,因此偏向锁的想法是一旦线程第一次获得了监视对象,之后让监视对象“偏向”这个线程,之后的多次调用则可以避免CAS操作.

在锁对象的对象头中JavaThread*,如果是空,第一次获取锁的时候,就将自身的ThreadId写入到JavaThread*内,将锁头内的是否偏向锁的状态位置1.这样下次获取锁的时候,直接检查ThreadId是否和自身线程Id一致,如果一致,则认为当前线程已经获取了锁,因此不需再次获取锁,略过了轻量级锁和重量级锁的加锁阶段

注意:当锁有竞争关系的时候,需要解除偏向锁,进入轻量级锁

每一个线程在准备获取共享资源时

  • 第一步,检查MarkWord里面是不是放的自己的ThreadId,如果是,表示当前线程是处于"偏向锁",跳过轻量级锁直接执行同步体

获得偏向锁如下图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p9onuqZ3-1570264186289)(evernotecid://DE37D598-33A4-4708-800A-6D4CF841DDE2/wwwevernotecom/149352153/ENResource/p324)]

4.3 轻量级锁和重量级锁

  • 第二步,如果MarkWord不是自己的ThreadId,锁升级,这个时候用CAS来执行切换,新的线程根据MarkWord里面现有的ThreadId,通知之前线程暂停,之前线程将Markword的内容置为空.
  • 第三步,两个线程都把对象的HashCode复制到自己新建的用于存储锁的记录空间,接着开始通过CAS操作,把共享对象的MarKword的内容修改为自己新建的记录空间的地址的方式竞争MarkWord.
  • 第四步,第三步中成功执行CAS的获得资源,失败的则进入自旋.
  • 第五步,自旋的线程在自旋过程中,成功获得资源(即之前获的资源的线程执行完成并释放了共享资源),则整个状态依然处于轻量级锁的状态,如果自旋失败第六步,进入重量级锁的状态,这个时候,自旋的线程进行阻塞,等待之前线程执行完成并唤醒自己.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Zo2O0nVl-1570264186290)(evernotecid://DE37D598-33A4-4708-800A-6D4CF841DDE2/wwwevernotecom/149352153/ENResource/p325)]

在"分配空间并复制Mark Word到栈"步骤中,详细情况如下
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lbop3EaF-1570264186290)(evernotecid://DE37D598-33A4-4708-800A-6D4CF841DDE2/wwwevernotecom/149352153/ENResource/p352)]

5. wait/notify实现线程通信

wait,notify,notify all是线程的通信机制

线程A和线程B先后在获取到对象锁后执行wait()让线程释放对象锁进入等待队列,等线程C获取到对象锁后执行notifyAll()方法后,线程A和线程B被唤醒后抢对象锁,先后执行完synchronized代码块

package com.zhunongyun.toalibaba.distributed.concurrent.programming.threadcommunication;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 线程通信,执行wait()方法
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ThreadA extends Thread {

    private Object lock;

    @Override
    public void run() {
        synchronized (lock) {
            try {
                System.out.println("线程ThreadA 开始执行");
                System.out.println("线程ThreadA 开始执行锁对象lock的wait()方法");
                lock.wait();
                System.out.println("线程ThreadA 结束执行");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}
package com.zhunongyun.toalibaba.distributed.concurrent.programming.threadcommunication;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 线程通信,执行wait()方法
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ThreadB extends Thread {

    private Object lock;

    @Override
    public void run() {
        synchronized (lock) {
            try {
                System.out.println("线程ThreadB 开始执行");
                System.out.println("线程ThreadB 开始执行锁对象lock的wait()方法");
                lock.wait();
                System.out.println("线程ThreadB 结束执行");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}
package com.zhunongyun.toalibaba.distributed.concurrent.programming.threadcommunication;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 线程通信,执行notifyAll()方法
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ThreadC extends Thread {

    private Object lock;

    @Override
    public void run() {
        synchronized (lock) {
            try {
                System.out.println("线程ThreadC 开始执行");
                System.out.println("线程ThreadC 开始执行锁对象lock的notifyAll()方法");
                lock.notifyAll();
                System.out.println("线程ThreadC 结束执行");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}
package com.zhunongyun.toalibaba.distributed.concurrent.programming.threadcommunication;

/**
 * 线程通信测试类
 */
public class ThreadCommunicationTest {

    public static void main(String[] args) {
        Object lock = new Object();

        new ThreadA(lock).start();
        new ThreadB(lock).start();
        new ThreadC(lock).start();
    }
}

执行结果

线程ThreadA 开始执行
线程ThreadA 开始执行锁对象lock的wait()方法
线程ThreadB 开始执行
线程ThreadB 开始执行锁对象lock的wait()方法
线程ThreadC 开始执行
线程ThreadC 开始执行锁对象lock的notifyAll()方法
线程ThreadC 结束执行
线程ThreadB 结束执行
线程ThreadA 结束执行

如果把线程C中的notifyAll()换成notify(),会出现不一样的执行结果,线程A和线程C执行完了,线程B一直处于wait()状态

线程ThreadA 开始执行
线程ThreadA 开始执行锁对象lock的wait()方法
线程ThreadB 开始执行
线程ThreadB 开始执行锁对象lock的wait()方法
线程ThreadC 开始执行
线程ThreadC 开始执行锁对象lock的notifyAll()方法
线程ThreadC 结束执行
线程ThreadA 结束执行

6. 从C++源码看synchronized

6.1 同步和互斥

同步:多个线程并发访问共享资源时,保证同一时刻只有一个(信号量可以多个)线程使用.

实现同步的方法有很多,常见四种如下:

    1. 临界区(Critical Section又叫关键段):通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问,进程内可用.
    1. 互斥量:互斥量用于线程的互斥.只能为0/1.一个互斥量只能用于一个资源的互斥访问,可跨进程使用.
    1. 信号量:信号线用于线程的同步.可以为非负整数,可实现多个同类资源的多线程互斥和同步.当信号量为单值信号量是,也可以完成一个资源的互斥访问.可跨进程使用.
    1. 事件:用来通知线程有一些事件已发生,从而启动后继任务的开始,可跨进程使用.

synchronized的底层实现就用到了临界区和互斥锁(重量级锁的情况下)这两个概念.

看过了对象头MarkWord.现在我们从C++源码来剖析具体的数据结构和获取释放锁的过程.

6.2 C++中的监视器锁数据结构

oopDesc--继承-->markOopDesc--方法monitor()-->ObjectMonitor-->enter,exit获取,释放锁

oopDesc类
openjdk\hotspot\src\share\vm\oops\oop.hpp下oopDesc类是JVM对象的顶级基类,故每个object都包含markOop.如下图所示:

class oopDesc {
  friend class VMStructs;
 private:
  volatile markOop  _mark;//markOop:Mark Word标记字段
  union _metadata {
    Klass*      _klass;//对象类型元数据的指针
    narrowKlass _compressed_klass;
  } _metadata;

  // Fast access to barrier set.  Must be initialized.
  static BarrierSet* _bs;

 public:
  markOop  mark() const         { return _mark; }
  markOop* mark_addr() const    { return (markOop*) &_mark; }

  void set_mark(volatile markOop m)      { _mark = m;   }

  void    release_set_mark(markOop m);
  markOop cas_set_mark(markOop new_mark, markOop old_mark);

  // Used only to re-initialize the mark word (e.g., of promoted
  // objects during a GC) -- requires a valid klass pointer
  void init_mark();

  Klass* klass() const;
  Klass* klass_or_null() const volatile;
  Klass** klass_addr();
  narrowKlass* compressed_klass_addr();
....省略...
}

markOopDesc类

openjdk\hotspot\src\share\vm\oops\markOop.hpp下markOopDesc继承自oopDesc,并拓展了自己的方法monitor(),如下图

ObjectMonitor* monitor() const {
    assert(has_monitor(), "check");
    // Use xor instead of &~ to provide one extra tag-bit check.
    return (ObjectMonitor*) (value() ^ monitor_value);
}

该方法返回一个ObjectMonitor*对象指针.

其中value()这样定义:

uintptr_t value() const { return (uintptr_t) this; }

可知:将this转换成一个指针宽度的整数(uintptr_t),然后进行"异或"位操作.

monitor_value是常量

enum {
    locked_value             = 0,//00偏向锁
    unlocked_value           = 1,//01无锁
    monitor_value            = 2,//10监视器锁,又叫重量级锁
    marked_value             = 3,//11GC标记
    biased_lock_pattern      = 5 //101偏向锁
};

指针低2位00,异或10,结果还是10.(拿一个模板为00的数,异或一个二位数=数本身.因为异或:“相同为0,不同为1”.操作)

ObjectMonitor类

在HotSpot虚拟机中,最终采用ObjectMonitor类实现monitor.
openjdk\hotspot\src\share\vm\runtime\objectMonitor.hpp源码如下:

ObjectMonitor() {
    _header       = NULL;//markOop对象头
    _count        = 0;
    _waiters      = 0,//等待线程数
    _recursions   = 0;//重入次数
    _object       = NULL;//监视器锁寄生的对象。锁不是平白出现的,而是寄托存储于对象中。
    _owner        = NULL;//指向获得ObjectMonitor对象的线程或基础锁
    _WaitSet      = NULL;//处于wait状态的线程,会被加入到wait set;
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;
    FreeNext      = NULL ;
    _EntryList    = NULL ;//处于等待锁block状态的线程,会被加入到entry set;
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;// _owner is (Thread *) vs SP/BasicLock
    _previous_owner_tid = 0;// 监视器前一个拥有者线程的ID
  }

每个线程都有两个ObjectMonitor对象列表,分别为free和used列表,如果当前free列表为空,线程将向全局globallist请求分配ObjectMonitor.

ObjectMonitor对象中有两个队列:_WaitSet和_EntryList,用来保存ObjectWaiter对象列表;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PKyyIsz9-1570264186291)(evernotecid://DE37D598-33A4-4708-800A-6D4CF841DDE2/wwwevernotecom/149352153/ENResource/p348)]

6.3 获取锁流程

synchronized关键字修饰的代码段,在JVM被编译为monitorenter,monitorexit指令来获取和释放互斥锁…

解释器执行monitorenter时会进入到InterpreterRuntime.cpp的InterpreterRuntime::monitorenter函数,具体实现如下:

IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorenter(JavaThread* thread, BasicObjectLock* elem))
#ifdef ASSERT
  thread->last_frame().interpreter_frame_verify_monitor(elem);
#endif
  if (PrintBiasedLockingStatistics) {
    Atomic::inc(BiasedLocking::slow_path_entry_count_addr());
  }
  Handle h_obj(thread, elem->obj());
  assert(Universe::heap()->is_in_reserved_or_null(h_obj()),
         "must be NULL or an object");
  if (UseBiasedLocking) {//标识虚拟机是否开启偏向锁功能,默认开启
    // Retry fast entry if bias is revoked to avoid unnecessary inflation
    ObjectSynchronizer::fast_enter(h_obj, elem->lock(), true, CHECK);
  } else {
    ObjectSynchronizer::slow_enter(h_obj, elem->lock(), CHECK);
  }
  assert(Universe::heap()->is_in_reserved_or_null(elem->obj()),
         "must be NULL or an object");
#ifdef ASSERT
  thread->last_frame().interpreter_frame_verify_monitor(elem);
#endif
IRT_END

先看一下入参:

  • 1.JavaThreadthread指向java中的当前线程;
  • 2.BasicObjectLock基础对象锁:包含一个BasicLock和一个指向Object对象的指针oop.

openjdk\hotspot\src\share\vm\runtime\basicLock.hpp中BasicObjectLock类源码如下:

class BasicObjectLock VALUE_OBJ_CLASS_SPEC {
  friend class VMStructs;
 private:
  BasicLock _lock;                                    // the lock, must be double word aligned
  oop       _obj;                                     // object holds the lock;

 public:
  // Manipulation
  oop      obj() const                                { return _obj;  }
  void set_obj(oop obj)                               { _obj = obj; }
  BasicLock* lock()                                   { return &_lock; }

  // Note: Use frame::interpreter_frame_monitor_size() for the size of BasicObjectLocks
  //       in interpreter activation frames since it includes machine-specific padding.
  static int size()                                   { return sizeof(BasicObjectLock)/wordSize; }

  // GC support
  void oops_do(OopClosure* f) { f->do_oop(&_obj); }

  static int obj_offset_in_bytes()                    { return offset_of(BasicObjectLock, _obj);  }
  static int lock_offset_in_bytes()                   { return offset_of(BasicObjectLock, _lock); }
};
  • 3.BasicLock类型_lock对象主要用来保存:指向Object对象的对象头数据;
    basicLock.hpp中BasicLock源码如下:
class BasicLock VALUE_OBJ_CLASS_SPEC {
  friend class VMStructs;
 private:
  volatile markOop _displaced_header;//markOop是不是很熟悉?1.2节中讲解对象头时就是分析的markOop源码
 public:
  markOop      displaced_header() const               { return _displaced_header; }
  void         set_displaced_header(markOop header)   { _displaced_header = header; }

  void print_on(outputStream* st) const;

  // move a basic lock (used during deoptimization
  void move_to(oop obj, BasicLock* dest);

  static int displaced_header_offset_in_bytes()       { return offset_of(BasicLock, _displaced_header); }
};

偏向锁的获取ObjectSynchronizer::fast_enter

在HotSpot中,偏向锁的入口位于openjdk\hotspot\src\share\vm\runtime\synchronizer.cpp文件的ObjectSynchronizer::fast_enter函数:

void ObjectSynchronizer::fast_enter(Handle obj, BasicLock* lock, bool attempt_rebias, TRAPS) {
 if (UseBiasedLocking) {
    if (!SafepointSynchronize::is_at_safepoint()) {
      BiasedLocking::Condition cond = BiasedLocking::revoke_and_rebias(obj, attempt_rebias, THREAD);
      if (cond == BiasedLocking::BIAS_REVOKED_AND_REBIASED) {
        return;
      }
    } else {
      assert(!attempt_rebias, "can not rebias toward VM thread");
      BiasedLocking::revoke_at_safepoint(obj);
    }
    assert(!obj->mark()->has_bias_pattern(), "biases should be revoked by now");
 }
 //轻量级锁
 slow_enter (obj, lock, THREAD) ;
}

偏向锁的获取由BiasedLocking::revoke_and_rebias方法实现,由于实现比较长,就不贴代码了,实现逻辑如下:

  • 1.通过markOopmark=obj->mark()获取对象的markOop数据mark,即对象头的MarkWord;
  • 2.判断mark是否为可偏向状态,即mark的偏向锁标志位为1,锁标志位为01;
  • 3.判断mark中JavaThread的状态:如果为空,则进入步骤(4);如果指向当前线程,则执行同步代码块;如果指向其它线程,进入步骤(5);
  • 4.通过CAS原子指令设置mark中JavaThread为当前线程ID,如果执行CAS成功,则执行同步代码块,否则进入步骤(5);
  • 5.如果执行CAS失败,表示当前存在多个线程竞争锁,当达到全局安全点(safepoint),获得偏向锁的线程被挂起,撤销偏向锁,并升级为轻量级,升级完成后被阻塞在安全点的线程继续执行同步代码块;

6.4 偏向锁的撤销

只有当其它线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,偏向锁的撤销由BiasedLocking::revoke_at_safepoint方法实现:

void BiasedLocking::revoke_at_safepoint(Handle h_obj) {
  assert(SafepointSynchronize::is_at_safepoint(), "must only be called while at safepoint");//校验全局安全点
  oop obj = h_obj();
  HeuristicsResult heuristics = update_heuristics(obj, false);
  if (heuristics == HR_SINGLE_REVOKE) {
    revoke_bias(obj, false, false, NULL);
  } else if ((heuristics == HR_BULK_REBIAS) ||
             (heuristics == HR_BULK_REVOKE)) {
    bulk_revoke_or_rebias_at_safepoint(obj, (heuristics == HR_BULK_REBIAS), false, NULL);
  }
  clean_up_cached_monitor_info();
}
  • 偏向锁的撤销动作必须等待全局安全点;
  • 暂停拥有偏向锁的线程,判断锁对象是否处于被锁定状态;
  • 撤销偏向锁,恢复到无锁(标志位为01)或轻量级锁(标志位为00)的状态;

偏向锁在Java1.6之后是默认启用的,但在应用程序启动几秒钟之后才激活,可以使用-XX:BiasedLockingStartupDelay=0参数关闭延迟,如果确定应用程序中所有锁通常情况下处于竞争状态,可以通过XX:-UseBiasedLocking=false参数关闭偏向锁.

6.5 轻量级锁的获取

当关闭偏向锁功能,或多个线程竞争偏向锁导致偏向锁升级为轻量级锁,会尝试获取轻量级锁,其入口位于ObjectSynchronizer::slow_enter

void ObjectSynchronizer::slow_enter(Handle obj, BasicLock* lock, TRAPS) {
  markOop mark = obj->mark();
  assert(!mark->has_bias_pattern(), "should not see bias pattern here");

  if (mark->is_neutral()) {//是否为无锁状态001
    // Anticipate successful CAS -- the ST of the displaced mark must
    // be visible <= the ST performed by the CAS.
    lock->set_displaced_header(mark);
    if (mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)) {//CAS成功,释放栈锁
      TEVENT (slow_enter: release stacklock) ;
      return ;
    }
    // Fall through to inflate() ...
  } else
  if (mark->has_locker() && THREAD->is_lock_owned((address)mark->locker())) {
    assert(lock != mark->locker(), "must not re-lock the same lock");
    assert(lock != (BasicLock*)obj->mark(), "don't relock with same BasicLock");
    lock->set_displaced_header(NULL);
    return;
  }

#if 0
  // The following optimization isn't particularly useful.
  if (mark->has_monitor() && mark->monitor()->is_entered(THREAD)) {
    lock->set_displaced_header (NULL) ;
    return ;
  }
#endif

  // The object header will never be displaced to this lock,
  // so it does not matter what the value is, except that it
  // must be non-zero to avoid looking like a re-entrant lock,
  // and must not look locked either.
  lock->set_displaced_header(markOopDesc::unused_mark());
  ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD);
}
  • 1.markOopmark=obj->mark()方法获取对象的markOop数据mark;
  • 2.mark->is_neutral()方法判断mark是否为无锁状态:mark的偏向锁标志位为0,锁标志位为01;
  • 3.如果mark处于无锁状态,则进入步骤(4),否则执行步骤(6);
  • 4.把mark保存到BasicLock对象的_displaced_header字段;
  • 5.通过CAS尝试将MarkWord更新为指向BasicLock对象的指针,如果更新成功,表示竞争到锁,则执行同步代码,否则执行步骤(6);
  • 6.如果当前mark处于加锁状态,且mark中的ptr指针指向当前线程的栈帧,则执行同步代码,否则说明有多个线程竞争轻量级锁,轻量级锁需要膨胀升级为重量级锁;

假设线程A和B同时执行到临界区if(mark->is_neutral()):

  • 1.线程AB都把MarkWord复制到各自的_displaced_header字段,该数据保存在线程的栈帧上,是线程私有的;
  • 2.Atomic::cmpxchg_ptr原子操作保证只有一个线程可以把指向栈帧的指针复制到MarkWord,假设此时线程A执行成功,并返回继续执行同步代码块;
  • 3.线程B执行失败,退出临界区,通过ObjectSynchronizer::inflate方法开始膨胀锁;

6.6 轻量级锁的释放

轻量级锁的释放通过ObjectSynchronizer::slow_exit—>调用ObjectSynchronizer::fast_exit完成.

void ObjectSynchronizer::fast_exit(oop object, BasicLock* lock, TRAPS) {
  assert(!object->mark()->has_bias_pattern(), "should not see bias pattern here");
  // if displaced header is null, the previous enter is recursive enter, no-op
  markOop dhw = lock->displaced_header();
  markOop mark ;
  if (dhw == NULL) {
     // Recursive stack-lock.
     // Diagnostics -- Could be: stack-locked, inflating, inflated.
     mark = object->mark() ;
     assert (!mark->is_neutral(), "invariant") ;
     if (mark->has_locker() && mark != markOopDesc::INFLATING()) {
        assert(THREAD->is_lock_owned((address)mark->locker()), "invariant") ;
     }
     if (mark->has_monitor()) {
        ObjectMonitor * m = mark->monitor() ;
        assert(((oop)(m->object()))->mark() == mark, "invariant") ;
        assert(m->is_entered(THREAD), "invariant") ;
     }
     return ;
  }

  mark = object->mark() ;

  // If the object is stack-locked by the current thread, try to
  // swing the displaced header from the box back to the mark.
  if (mark == (markOop) lock) {
     assert (dhw->is_neutral(), "invariant") ;
     if ((markOop) Atomic::cmpxchg_ptr (dhw, object->mark_addr(), mark) == mark) {//成功的释放了锁
        TEVENT (fast_exit: release stacklock) ;
        return;
     }
  }

  ObjectSynchronizer::inflate(THREAD, object)->exit (true, THREAD) ;//锁膨胀升级
}
  • 1.确保处于偏向锁状态时不会执行这段逻辑;
  • 2.取出在获取轻量级锁时保存在BasicLock对象的mark数据dhw;
  • 3.通过CAS尝试把dhw替换到当前的MarkWord,如果CAS成功,说明成功的释放了锁,否则执行步骤(4);
  • 4.如果CAS失败,说明有其它线程在尝试获取该锁,这时需要将该锁升级为重量级锁,并释放;

6.7 重量级锁

重量级锁通过对象内部的监视器(monitor)实现,其中monitor的本质是依赖于底层操作系统的MutexLock实现,操作系统实现线程之间的切换需要从用户态到内核态的切换,切换成本非常高.

6.8 锁膨胀过程

锁的膨胀过程通过ObjectSynchronizer::inflate函数实现

ObjectMonitor * ATTR ObjectSynchronizer::inflate (Thread * Self, oop object) {
  // Inflate mutates the heap ...
  // Relaxing assertion for bug 6320749.
  assert (Universe::verify_in_progress() ||
          !SafepointSynchronize::is_at_safepoint(), "invariant") ;

  for (;;) {//自旋
      const markOop mark = object->mark() ;
      assert (!mark->has_bias_pattern(), "invariant") ;

      // The mark can be in one of the following states:
      // *  Inflated     - just return
      // *  Stack-locked - coerce it to inflated
      // *  INFLATING    - busy wait for conversion to complete
      // *  Neutral      - aggressively inflate the object.
      // *  BIASED       - Illegal.  We should never see this

      // CASE: inflated已膨胀,即重量级锁
      if (mark->has_monitor()) {//判断当前是否为重量级锁
          ObjectMonitor * inf = mark->monitor() ;//获取指向ObjectMonitor的指针
          assert (inf->header()->is_neutral(), "invariant");
          assert (inf->object() == object, "invariant") ;
          assert (ObjectSynchronizer::verify_objmon_isinpool(inf), "monitor is invalid");
          return inf ;
      }

      // CASE: inflation in progress - inflating over a stack-lock.膨胀等待(其他线程正在从轻量级锁转为膨胀锁)
      // Some other thread is converting from stack-locked to inflated.
      // Only that thread can complete inflation -- other threads must wait.
      // The INFLATING value is transient.
      // Currently, we spin/yield/park and poll the markword, waiting for inflation to finish.
      // We could always eliminate polling by parking the thread on some auxiliary list.
      if (mark == markOopDesc::INFLATING()) {
         TEVENT (Inflate: spin while INFLATING) ;
         ReadStableMark(object) ;
         continue ;
      }

      // CASE: stack-locked栈锁(轻量级锁)
      // Could be stack-locked either by this thread or by some other thread.
      //
      // Note that we allocate the objectmonitor speculatively, _before_ attempting
      // to install INFLATING into the mark word.  We originally installed INFLATING,
      // allocated the objectmonitor, and then finally STed the address of the
      // objectmonitor into the mark.  This was correct, but artificially lengthened
      // the interval in which INFLATED appeared in the mark, thus increasing
      // the odds of inflation contention.
      //
      // We now use per-thread private objectmonitor free lists.
      // These list are reprovisioned from the global free list outside the
      // critical INFLATING...ST interval.  A thread can transfer
      // multiple objectmonitors en-mass from the global free list to its local free list.
      // This reduces coherency traffic and lock contention on the global free list.
      // Using such local free lists, it doesn't matter if the omAlloc() call appears
      // before or after the CAS(INFLATING) operation.
      // See the comments in omAlloc().

      if (mark->has_locker()) {
          ObjectMonitor * m = omAlloc (Self) ;//获取一个可用的ObjectMonitor
          // Optimistically prepare the objectmonitor - anticipate successful CAS
          // We do this before the CAS in order to minimize the length of time
          // in which INFLATING appears in the mark.
          m->Recycle();
          m->_Responsible  = NULL ;
          m->OwnerIsThread = 0 ;
          m->_recursions   = 0 ;
          m->_SpinDuration = ObjectMonitor::Knob_SpinLimit ;   // Consider: maintain by type/class

          markOop cmp = (markOop) Atomic::cmpxchg_ptr (markOopDesc::INFLATING(), object->mark_addr(), mark) ;
          if (cmp != mark) {//CAS失败//CAS失败,说明冲突了,自旋等待//CAS失败,说明冲突了,自旋等待//CAS失败,说明冲突了,自旋等待
             omRelease (Self, m, true) ;//释放监视器锁
             continue ;       // Interference -- just retry
          }

          // We've successfully installed INFLATING (0) into the mark-word.
          // This is the only case where 0 will appear in a mark-work.
          // Only the singular thread that successfully swings the mark-word
          // to 0 can perform (or more precisely, complete) inflation.
          //
          // Why do we CAS a 0 into the mark-word instead of just CASing the
          // mark-word from the stack-locked value directly to the new inflated state?
          // Consider what happens when a thread unlocks a stack-locked object.
          // It attempts to use CAS to swing the displaced header value from the
          // on-stack basiclock back into the object header.  Recall also that the
          // header value (hashcode, etc) can reside in (a) the object header, or
          // (b) a displaced header associated with the stack-lock, or (c) a displaced
          // header in an objectMonitor.  The inflate() routine must copy the header
          // value from the basiclock on the owner's stack to the objectMonitor, all
          // the while preserving the hashCode stability invariants.  If the owner
          // decides to release the lock while the value is 0, the unlock will fail
          // and control will eventually pass from slow_exit() to inflate.  The owner
          // will then spin, waiting for the 0 value to disappear.   Put another way,
          // the 0 causes the owner to stall if the owner happens to try to
          // drop the lock (restoring the header from the basiclock to the object)
          // while inflation is in-progress.  This protocol avoids races that might
          // would otherwise permit hashCode values to change or "flicker" for an object.
          // Critically, while object->mark is 0 mark->displaced_mark_helper() is stable.
          // 0 serves as a "BUSY" inflate-in-progress indicator.


          // fetch the displaced mark from the owner's stack.
          // The owner can't die or unwind past the lock while our INFLATING
          // object is in the mark.  Furthermore the owner can't complete
          // an unlock on the object, either.
          markOop dmw = mark->displaced_mark_helper() ;
          assert (dmw->is_neutral(), "invariant") ;
          //CAS成功,设置ObjectMonitor的_header、_owner和_object等
          // Setup monitor fields to proper values -- prepare the monitor
          m->set_header(dmw) ;

          // Optimization: if the mark->locker stack address is associated
          // with this thread we could simply set m->_owner = Self and
          // m->OwnerIsThread = 1. Note that a thread can inflate an object
          // that it has stack-locked -- as might happen in wait() -- directly
          // with CAS.  That is, we can avoid the xchg-NULL .... ST idiom.
          m->set_owner(mark->locker());
          m->set_object(object);
          // TODO-FIXME: assert BasicLock->dhw != 0.

          // Must preserve store ordering. The monitor state must
          // be stable at the time of publishing the monitor address.
          guarantee (object->mark() == markOopDesc::INFLATING(), "invariant") ;
          object->release_set_mark(markOopDesc::encode(m));

          // Hopefully the performance counters are allocated on distinct cache lines
          // to avoid false sharing on MP systems ...
          if (ObjectMonitor::_sync_Inflations != NULL) ObjectMonitor::_sync_Inflations->inc() ;
          TEVENT(Inflate: overwrite stacklock) ;
          if (TraceMonitorInflation) {
            if (object->is_instance()) {
              ResourceMark rm;
              tty->print_cr("Inflating object " INTPTR_FORMAT " , mark " INTPTR_FORMAT " , type %s",
                (void *) object, (intptr_t) object->mark(),
                object->klass()->external_name());
            }
          }
          return m ;
      }

      // CASE: neutral 无锁
      // TODO-FIXME: for entry we currently inflate and then try to CAS _owner.
      // If we know we're inflating for entry it's better to inflate by swinging a
      // pre-locked objectMonitor pointer into the object header.   A successful
      // CAS inflates the object *and* confers ownership to the inflating thread.
      // In the current implementation we use a 2-step mechanism where we CAS()
      // to inflate and then CAS() again to try to swing _owner from NULL to Self.
      // An inflateTry() method that we could call from fast_enter() and slow_enter()
      // would be useful.

      assert (mark->is_neutral(), "invariant");
      ObjectMonitor * m = omAlloc (Self) ;
      // prepare m for installation - set monitor to initial state
      m->Recycle();
      m->set_header(mark);
      m->set_owner(NULL);
      m->set_object(object);
      m->OwnerIsThread = 1 ;
      m->_recursions   = 0 ;
      m->_Responsible  = NULL ;
      m->_SpinDuration = ObjectMonitor::Knob_SpinLimit ;       // consider: keep metastats by type/class

      if (Atomic::cmpxchg_ptr (markOopDesc::encode(m), object->mark_addr(), mark) != mark) {
          m->set_object (NULL) ;
          m->set_owner  (NULL) ;
          m->OwnerIsThread = 0 ;
          m->Recycle() ;
          omRelease (Self, m, true) ;
          m = NULL ;
          continue ;
          // interference - the markword changed - just retry.
          // The state-transitions are one-way, so there's no chance of
          // live-lock -- "Inflated" is an absorbing state.
      }

      // Hopefully the performance counters are allocated on distinct
      // cache lines to avoid false sharing on MP systems ...
      if (ObjectMonitor::_sync_Inflations != NULL) ObjectMonitor::_sync_Inflations->inc() ;
      TEVENT(Inflate: overwrite neutral) ;
      if (TraceMonitorInflation) {
        if (object->is_instance()) {
          ResourceMark rm;
          tty->print_cr("Inflating object " INTPTR_FORMAT " , mark " INTPTR_FORMAT " , type %s",
            (void *) object, (intptr_t) object->mark(),
            object->klass()->external_name());
        }
      }
      return m ;
  }
}

膨胀过程的实现比较复杂,大概实现过程如下:

  • 1.整个膨胀过程在自旋下完成;
  • 2.mark->has_monitor()方法判断当前是否为重量级锁(上图18-25行),即MarkWord的锁标识位为10,如果当前状态为重量级锁,执行步骤(3),否则执行步骤(4);
  • 3.mark->monitor()方法获取指向ObjectMonitor的指针,并返回,说明膨胀过程已经完成;
  • 4.如果当前锁处于膨胀中(上图33-37行),说明该锁正在被其它线程执行膨胀操作,则当前线程就进行自旋等待锁膨胀完成,这里需要注意一点,虽然是自旋操作,但不会一直占用cpu资源,每隔一段时间会通过os::NakedYield方法放弃cpu资源,或通过park方法挂起;如果其他线程完成锁的膨胀操作,则退出自旋并返回;
  • 5.如果当前是轻量级锁状态(上图58-138行),即锁标识位为00,膨胀过程如下:

*通过omAlloc方法,获取一个可用的ObjectMonitormonitor,并重置monitor数据;
*通过CAS尝试将MarkWord设置为markOopDesc:INFLATING,标识当前锁正在膨胀中,如果CAS失败,说明同一时刻其它线程已经将MarkWord设置为markOopDesc:INFLATING,当前线程进行自旋等待膨胀完成;
*如果CAS成功,设置monitor的各个字段:_header,_owner和_object等,并返回;

  • 6.如果是无锁(中立,上图150-186行),重置监视器值;

6.9 monitor竞争

当锁膨胀完成并返回对应的monitor时,并不表示该线程竞争到了锁,真正的锁竞争发生在ObjectMonitor::enter方法中.

void ATTR ObjectMonitor::enter(TRAPS) {
  // The following code is ordered to check the most common cases first
  // and to reduce RTS->RTO cache line upgrades on SPARC and IA32 processors.
  Thread * const Self = THREAD ;
  void * cur ;

  cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL) ;
  if (cur == NULL) {//CAS成功
     // Either ASSERT _recursions == 0 or explicitly set _recursions = 0.
     assert (_recursions == 0   , "invariant") ;
     assert (_owner      == Self, "invariant") ;
     // CONSIDER: set or assert OwnerIsThread == 1
     return ;
  }

  if (cur == Self) {//重入锁
     // TODO-FIXME: check for integer overflow!  BUGID 6557169.
     _recursions ++ ;
     return ;
  }

  if (Self->is_lock_owned ((address)cur)) {
    assert (_recursions == 0, "internal state error");
    _recursions = 1 ;
    // Commute owner from a thread-specific on-stack BasicLockObject address to
    // a full-fledged "Thread *".
    _owner = Self ;
    OwnerIsThread = 1 ;
    return ;
  }

  // We've encountered genuine contention.
  assert (Self->_Stalled == 0, "invariant") ;
  Self->_Stalled = intptr_t(this) ;

  // Try one round of spinning *before* enqueueing Self
  // and before going through the awkward and expensive state
  // transitions.  The following spin is strictly optional ...
  // Note that if we acquire the monitor from an initial spin
  // we forgo posting JVMTI events and firing DTRACE probes.
  if (Knob_SpinEarly && TrySpin (Self) > 0) {
     assert (_owner == Self      , "invariant") ;
     assert (_recursions == 0    , "invariant") ;
     assert (((oop)(object()))->mark() == markOopDesc::encode(this), "invariant") ;
     Self->_Stalled = 0 ;
     return ;
  }

  assert (_owner != Self          , "invariant") ;
  assert (_succ  != Self          , "invariant") ;
  assert (Self->is_Java_thread()  , "invariant") ;
  JavaThread * jt = (JavaThread *) Self ;
  assert (!SafepointSynchronize::is_at_safepoint(), "invariant") ;
  assert (jt->thread_state() != _thread_blocked   , "invariant") ;
  assert (this->object() != NULL  , "invariant") ;
  assert (_count >= 0, "invariant") ;

  // Prevent deflation at STW-time.  See deflate_idle_monitors() and is_busy().
  // Ensure the object-monitor relationship remains stable while there's contention.
  Atomic::inc_ptr(&_count);

  EventJavaMonitorEnter event;

  { // Change java thread status to indicate blocked on monitor enter.
    JavaThreadBlockedOnMonitorEnterState jtbmes(jt, this);

    DTRACE_MONITOR_PROBE(contended__enter, this, object(), jt);
    if (JvmtiExport::should_post_monitor_contended_enter()) {
      JvmtiExport::post_monitor_contended_enter(jt, this);
    }

    OSThreadContendState osts(Self->osthread());
    ThreadBlockInVM tbivm(jt);

    Self->set_current_pending_monitor(this);

    // TODO-FIXME: change the following for(;;) loop to straight-line code.
    for (;;) {
      jt->set_suspend_equivalent();
      // cleared by handle_special_suspend_equivalent_condition()
      // or java_suspend_self()

      EnterI (THREAD) ;

...省略...139 }

6.10 monitor等待

monitor竞争失败的线程,通过自旋执行ObjectMonitor::EnterI方法等待锁的释放,EnterI方法的部分逻辑实现如下:

ObjectWaiter node(Self) ;
    Self->_ParkEvent->reset() ;
    node._prev   = (ObjectWaiter *) 0xBAD ;
    node.TState  = ObjectWaiter::TS_CXQ ;

    // Push "Self" onto the front of the _cxq.
    // Once on cxq/EntryList, Self stays on-queue until it acquires the lock.
    // Note that spinning tends to reduce the rate at which threads
    // enqueue and dequeue on EntryList|cxq.
    ObjectWaiter * nxt ;
    for (;;) {
        node._next = nxt = _cxq ;
        if (Atomic::cmpxchg_ptr (&node, &_cxq, nxt) == nxt) break ;

        // Interference - the CAS failed because _cxq changed.  Just retry.
        // As an optional optimization we retry the lock.
        if (TryLock (Self) > 0) {
            assert (_succ != Self         , "invariant") ;
            assert (_owner == Self        , "invariant") ;
            assert (_Responsible != Self  , "invariant") ;
            return ;
        }
    }
  • 1.当前线程被封装成ObjectWaiter对象node,状态设置成ObjectWaiter::TS_CXQ;
  • 2.在for循环中,通过CAS把node节点push到_cxq列表中,同一时刻可能有多个线程把自己的node节点push到_cxq列表中;
  • 3.node节点push到_cxq列表之后,通过自旋尝试获取锁,如果还是没有获取到锁,则通过park将当前线程挂起,等待被唤醒,实现如下:
for (;;) {

        if (TryLock (Self) > 0) break ;
        assert (_owner != Self, "invariant") ;

        if ((SyncFlags & 2) && _Responsible == NULL) {
           Atomic::cmpxchg_ptr (Self, &_Responsible, NULL) ;
        }

        // park self
        if (_Responsible == Self || (SyncFlags & 1)) {
            TEVENT (Inflated enter - park TIMED) ;
            Self->_ParkEvent->park ((jlong) RecheckInterval) ;
            // Increase the RecheckInterval, but clamp the value.
            RecheckInterval *= 8 ;
            if (RecheckInterval > 1000) RecheckInterval = 1000 ;
        } else {
            TEVENT (Inflated enter - park UNTIMED) ;
            Self->_ParkEvent->park() ;//当前线程挂起
        }

        if (TryLock(Self) > 0) break ;

        // The lock is still contested.
        // Keep a tally of the # of futile wakeups.
        // Note that the counter is not protected by a lock or updated by atomics.
        // That is by design - we trade "lossy" counters which are exposed to
        // races during updates for a lower probe effect.
        TEVENT (Inflated enter - Futile wakeup) ;
        if (ObjectMonitor::_sync_FutileWakeups != NULL) {
           ObjectMonitor::_sync_FutileWakeups->inc() ;
        }
        ++ nWakeups ;

        // Assuming this is not a spurious wakeup we'll normally find _succ == Self.
        // We can defer clearing _succ until after the spin completes
        // TrySpin() must tolerate being called with _succ == Self.
        // Try yet another round of adaptive spinning.
        if ((Knob_SpinAfterFutile & 1) && TrySpin (Self) > 0) break ;

        // We can find that we were unpark()ed and redesignated _succ while
        // we were spinning.  That's harmless.  If we iterate and call park(),
        // park() will consume the event and return immediately and we'll
        // just spin again.  This pattern can repeat, leaving _succ to simply
        // spin on a CPU.  Enable Knob_ResetEvent to clear pending unparks().
        // Alternately, we can sample fired() here, and if set, forgo spinning
        // in the next iteration.

        if ((Knob_ResetEvent & 1) && Self->_ParkEvent->fired()) {
           Self->_ParkEvent->reset() ;
           OrderAccess::fence() ;
        }
        if (_succ == Self) _succ = NULL ;

        // Invariant: after clearing _succ a thread *must* retry _owner before parking.
        OrderAccess::fence() ;
    }
  • 4.当该线程被唤醒时,会从挂起的点继续执行,通过ObjectMonitor::TryLock尝试获取锁,TryLock方法实现如下:
int ObjectMonitor::TryLock (Thread * Self) {
   for (;;) {
      void * own = _owner ;
      if (own != NULL) return 0 ;
      if (Atomic::cmpxchg_ptr (Self, &_owner, NULL) == NULL) {//CAS成功,获取锁
         // Either guarantee _recursions == 0 or set _recursions = 0.
         assert (_recursions == 0, "invariant") ;
         assert (_owner == Self, "invariant") ;
         // CONSIDER: set or assert that OwnerIsThread == 1
         return 1 ;
      }
      // The lock had been free momentarily, but we lost the race to the lock.
      // Interference -- the CAS failed.
      // We can either return -1 or retry.
      // Retry doesn't make as much sense because the lock was just acquired.
      if (true) return -1 ;
   }
}

其本质就是通过CAS设置monitor的_owner字段为当前线程,如果CAS成功,则表示该线程获取了锁,跳出自旋操作,执行同步代码,否则继续被挂起;

6.11 monitor释放

当某个持有锁的线程执行完同步代码块时,会进行锁的释放,给其它线程机会执行同步代码,在HotSpot中,通过退出monitor的方式实现锁的释放,并通知被阻塞的线程,具体实现位于ObjectMonitor::exit方法中.

void ATTR ObjectMonitor::exit(bool not_suspended, TRAPS) {
   Thread * Self = THREAD ;
   if (THREAD != _owner) {
     if (THREAD->is_lock_owned((address) _owner)) {
       // Transmute _owner from a BasicLock pointer to a Thread address.
       // We don't need to hold _mutex for this transition.
       // Non-null to Non-null is safe as long as all readers can
       // tolerate either flavor.
       assert (_recursions == 0, "invariant") ;
       _owner = THREAD ;
       _recursions = 0 ;
       OwnerIsThread = 1 ;
     } else {
       // NOTE: we need to handle unbalanced monitor enter/exit
       // in native code by throwing an exception.
       // TODO: Throw an IllegalMonitorStateException ?
       TEVENT (Exit - Throw IMSX) ;
       assert(false, "Non-balanced monitor enter/exit!");
       if (false) {
          THROW(vmSymbols::java_lang_IllegalMonitorStateException());
       }
       return;
     }
   }

   if (_recursions != 0) {
     _recursions--;        // this is simple recursive enter
     TEVENT (Inflated exit - recursive) ;
     return ;
   }
...省略...
  • 1.如果是重量级锁的释放,monitor中的_owner指向当前线程,即THREAD==_owner;
  • 2.根据不同的策略(由QMode指定),从cxq或EntryList中获取头节点,通过ObjectMonitor::ExitEpilog方法唤醒该节点封装的线程,唤醒操作最终由unpark完成,实现如下:
void ObjectMonitor::ExitEpilog (Thread * Self, ObjectWaiter * Wakee) {
   assert (_owner == Self, "invariant") ;

   // Exit protocol:
   // 1. ST _succ = wakee
   // 2. membar #loadstore|#storestore;
   // 2. ST _owner = NULL
   // 3. unpark(wakee)

   _succ = Knob_SuccEnabled ? Wakee->_thread : NULL ;
   ParkEvent * Trigger = Wakee->_event ;

   // Hygiene -- once we've set _owner = NULL we can't safely dereference Wakee again.
   // The thread associated with Wakee may have grabbed the lock and "Wakee" may be
   // out-of-scope (non-extant).
   Wakee  = NULL ;

   // Drop the lock
   OrderAccess::release_store_ptr (&_owner, NULL) ;
   OrderAccess::fence() ;                               // ST _owner vs LD in unpark()

   if (SafepointSynchronize::do_call_back()) {
      TEVENT (unpark before SAFEPOINT) ;
   }

   DTRACE_MONITOR_PROBE(contended__exit, this, object(), Self);
   Trigger->unpark() ;

   // Maintain stats and report events to JVMTI
   if (ObjectMonitor::_sync_Parks != NULL) {
      ObjectMonitor::_sync_Parks->inc() ;
   }
}
  • 3.被唤醒的线程,继续执行monitor的竞争;
发布了29 篇原创文章 · 获赞 10 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/csharpqiuqiu/article/details/102155872