Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >多线程四 并发中锁的原理

多线程四 并发中锁的原理

作者头像
用针戳左手中指指头
发布于 2021-01-29 03:06:46
发布于 2021-01-29 03:06:46
65800
代码可运行
举报
文章被收录于专栏:学习计划学习计划
运行总次数:0
代码可运行

先来引入锁的概念:

偏向锁:当前只有一个锁,无线程竞争的情况下,尽量减少不必要的轻量锁的执行路径。

偏向锁就是在运行过程中,对象的锁偏向某个线程,即在开启偏向锁的情况下,某个线程获得锁,当该线程下次想要获得锁时,不需要再获取锁(忽略synchronized关键字),直接执行代码

轻量锁:存在锁之间的竞争,但竞争的会很小,

重量锁:存在资源竞争


openjdk对对象头的注释

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//  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)

一个对象包括:

  • 对象头 ->mark word(64bit) + klass word (64bit)
  • 对象属性
  • 对齐 字节

java对象头在对象的不同状态下会有不同的表现形式,主要由:无所状态、加锁状态、gc标记状态。那么我们可以理解java当中的取锁其实可以理解是给对象上锁,也就是改变对象头的状态,如果上锁成功则进入同步代码块,但是java当代中的锁又分很多种,从上图可以看出大体分为偏向锁、轻量锁、重量锁三种锁状态。这三种锁的效率完全不同、关于效率的分析会在下文分析。

引入java对象布局工具

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.10</version>
</dependency>

1.打印jvm信息

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class TestA {
    private boolean a;
    private int b;
}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class TestSyncWord {
    public static void main(String[] args) {
        TestA a =  new TestA();
        System.out.println(VM.current().details());
        System.out.println(ClassLayout.parseClass(TestA.class).toPrintable(a));
    }
}

得到以下结果:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# Running 64-bit HotSpot VM.
# Using compressed oop with 3-bit shift.
# Using compressed klass with 3-bit shift.
# Objects are 8 bytes aligned.
// 对应:[Oop(Ordinary Object Pointer), boolean, byte, char, short, int, float, long, double]大小
# Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
# Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]

com.lry.thread.TestA object internals:
OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4           (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4           (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4       int TestA.b                                   0
     16     1   boolean TestA.a                                   false
     17     7           (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 7 bytes external = 7 bytes total

从上面结果可以看出:

  • 对象头占3*4=12 B
  • 实例对象数据占4+1=5B
  • 对齐占字节7B

整个对象占24B

对象头里面的专业术语查看:

http://openjdk.java.net/groups/hotspot/docs/HotSpotGlossary.html

Common structure at the beginning of every GC-managed heap object. (Every oop points to an object header.) Includes fundamental information about the heap object's layout, type, GC state, synchronization state, and identity hash code. Consists of two words. In arrays it is immediately followed by a length field. Note that both Java objects and VM-internal objects have a common object header format. 每个GC管理的堆对象开头的通用结构。 (每个oop都指向一个对象标头。)包括有关堆对象的布局,类型,GC状态,同步状态和标识哈希码的基本信息。 由两个词组成。 在数组中,紧随其后的是长度字段。 请注意,Java对象和VM内部对象都具有通用的对象标头格式。 klass pointer The second word of every object header. Points to another object (a metaobject) which describes the layout and behavior of the original object. For Java objects, the "klass" contains a C++ style "vtable". 每个对象标头的第二个字。 指向另一个对象(元对象),该对象描述原始对象的布局和行为。 对于Java对象,“容器”包含C ++样式“ vtable”。 mark word The first word of every object header. Usually a set of bitfields including synchronization state and identity hash code. May also be a pointer (with characteristic low bit encoding) to synchronization related information. During GC, may contain GC state bits. 每个对象标头的第一个单词。 通常,一组位域包括同步状态和标识哈希码。 也可以是指向与同步相关的信息的指针(具有特征性的低位编码)。 在GC期间,可能包含GC状态位。

可知一个对象头有mark word 和klass pointer两个部分组成(数组对象除外,数组对象的对象头还包含一个数组长度),那么一个java的对象头有多大呢?

从源码注释中可以知道一个mark word 是64bit,从上面代码解析的结果来看,对象头是12B,mark word固定为8B,那么klass pointer=4B;

接下来重点分析下mark word的信息;

在无锁的情况下mark word当中的前56bit存的是对象的hashcode,但是我们的结果,很明显的看不出想hashcode的迹象,是因为还没有计算,

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static void main(String[] args) {
    TestA a =  new TestA();
    System.out.println("计算前的hashcode");
    System.out.println(ClassLayout.parseInstance(a).toPrintable());
    // 计算hashcode
    System.out.println("16进制的hashcode:"+Integer.toHexString(a.hashCode()));
    System.out.println("计算后的hashcode");
    System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
计算前的hashcode
com.lry.thread.TestA object internals:
OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4           (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4           (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4       int TestA.b                                   0
     16     1   boolean TestA.a                                   false
     17     7           (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 7 bytes external = 7 bytes total


16进制的hashcode:5fdef03a
计算后的hashcode
com.lry.thread.TestA object internals:
OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           01 3a f0 de (00000001 00111010 11110000 11011110) (-554681855)
      4     4           (object header)                           5f 00 00 00 (01011111 00000000 00000000 00000000) (95)
      8     4           (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4       int TestA.b                                   0
     16     1   boolean TestA.a                                   false
     17     7           (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 7 bytes external = 7 bytes total

这里引入一个概念:小端对齐,那什么是小端对齐呢?

高内存地址放整数的高位,低内存地址放整数的低位,就叫小端对齐,Windows都是这样的。

从上面计算hashcode后的打印可以看到它的对象头发生变化;上面对象头中说有25位没有使用,31为是hashcode,剩下的对象的状态表示。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
0   4     (object header)       01 3a f0 de (00000001 00111010 11110000 11011110) (-554681855)
4   4     (object header)       5f 00 00 00 (01011111 00000000 00000000 00000000) (95)

除去00000001,剩下:00 00 00 5f de f0 3a 前3位,24位,没有使用。那5f de f0 3a就是他的hashcode,也和我们打印的hashcode一样。

那最后那个没有使用的字节是怎样的表示呢?

0 0000 0 01 没有使用 分代年龄 偏向 对象状态

对象状态一共有五种,分别是:无锁、偏向锁、轻量锁、重量锁、GC标识。

从上一个例子中可以看出,无锁状态表示是001

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static void main(String[] args) {
    TestA a = new TestA();
    synchronized (a) {
        System.out.println(ClassLayout.parseInstance(a).toPrintable());
    }
}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           98 f0 a3 02 (10011000 11110000 10100011 00000010) (44298392)
      4     4           (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4           (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4       int TestA.b                                   0
     16     1   boolean TestA.a                                   false
     17     7           (loss due to the next object alignment)

结果不是偏向锁,因为偏向标志位0,那么就是轻量级锁(000)

如果:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static void main(String[] args) {
    try {
        Thread.sleep(5000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    TestA a = new TestA();
    synchronized (a) {
        System.out.println(ClassLayout.parseInstance(a).toPrintable());
    }
}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
com.lry.thread.TestA object internals:
OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           05 68 15 03 (00000101 01101000 00010101 00000011) (51734533)
      4     4           (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4           (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4       int TestA.b                                   0
     16     1   boolean TestA.a                                   false
     17     7           (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 7 bytes external = 7 bytes total

偏向标志位位1,为偏向锁,这什么情况?

一般代码中的程序都是偏向锁,所有jvm在启动时对偏向锁延迟了,在启动后再加上锁,所有才会出现上面代码sleep,还可以使用下面参数来设置这个值。

-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0

jvm也提供了其他的一些参数,我们可以使用:-XX:+PrintFlagsFinal打印jvm的一些设置信息,可以看到延迟偏向的时间是4000,当然这个值也不是准确值,他只是延迟到这个时间去触发,执行的效率我们也不知道。

加锁于解锁的过程

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static void main(String[] args) {
    TestA a = new TestA();
    System.out.println("轻量级锁 start。。。");
    System.out.println(ClassLayout.parseInstance(a).toPrintable());

    synchronized (a) {
        a.count2();
        System.out.println(ClassLayout.parseInstance(a).toPrintable());
    }
    System.out.println("end ...");
    System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
轻量级锁 start。。。
com.lry.thread.TestA object internals:
OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4           (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4           (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4       int TestA.b                                   0
     16     1   boolean TestA.a                                   false
     17     7           (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 7 bytes external = 7 bytes total

com.lry.thread.TestA object internals:
OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           a8 f0 5a 03 (10101000 11110000 01011010 00000011) (56291496)
      4     4           (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4           (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4       int TestA.b                                   1
     16     1   boolean TestA.a                                   false
     17     7           (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 7 bytes external = 7 bytes total

end ...
com.lry.thread.TestA object internals:
OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4           (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4           (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4       int TestA.b                                   1
     16     1   boolean TestA.a                                   false
     17     7           (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 7 bytes external = 7 bytes total

资源加锁后,升级为轻量级锁,然后在释放锁后,变为无锁状态。

重量级锁的情况:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static void main(String[] args) {
    TestA a = new TestA();

    // 这里是无锁001
    System.out.println(ClassLayout.parseInstance(a).toPrintable());

    Thread t = new Thread(){
        @Override
        public void run() {
            synchronized (a) {
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                a.count2();
                System.out.println("sync  ing--------");
            }
        }
    };

    t.start();
    System.out.println("主线程睡眠前---------");
    System.out.println(ClassLayout.parseInstance(a).toPrintable());
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    System.out.println("线程t 启动后,主线程睡眠1秒--------");
    // 轻量锁 000;因为没设置偏向锁的延迟时间
    System.out.println(ClassLayout.parseInstance(a).toPrintable());

    synchronized (a) {
        System.out.println("主线程加锁调用方法--------");
        // 存在资源竞争,膨胀为重量级锁 010
        System.out.println(ClassLayout.parseInstance(a).toPrintable());
    }
    System.out.println("主线程调用在加锁后--------");
    // 在解锁后,标志未改变
    System.out.println(ClassLayout.parseInstance(a).toPrintable());

    System.gc();;
    System.out.println("GC 方法启动--------");
    // 资源回收,无锁 001
    System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
com.lry.thread.TestA object internals:
OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4           (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4           (object header)                           a0 c1 00 f8 (10100000 11000001 00000000 11111000) (-134168160)
     12     4       int TestA.b                                   0
     16     1   boolean TestA.a                                   false
     17     7           (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 7 bytes external = 7 bytes total


主线程睡眠前---------
com.lry.thread.TestA object internals:
OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           d8 ef 4e 20 (11011000 11101111 01001110 00100000) (542044120)
      4     4           (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4           (object header)                           a0 c1 00 f8 (10100000 11000001 00000000 11111000) (-134168160)
     12     4       int TestA.b                                   0
     16     1   boolean TestA.a                                   false
     17     7           (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 7 bytes external = 7 bytes total


线程t 启动后,主线程睡眠1--------
com.lry.thread.TestA object internals:
OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           d8 ef 4e 20 (11011000 11101111 01001110 00100000) (542044120)
      4     4           (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4           (object header)                           a0 c1 00 f8 (10100000 11000001 00000000 11111000) (-134168160)
     12     4       int TestA.b                                   0
     16     1   boolean TestA.a                                   false
     17     7           (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 7 bytes external = 7 bytes total


sync  ing--------
主线程加锁调用方法--------
com.lry.thread.TestA object internals:
OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           ea 12 79 1c (11101010 00010010 01111001 00011100) (477696746)
      4     4           (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4           (object header)                           a0 c1 00 f8 (10100000 11000001 00000000 11111000) (-134168160)
     12     4       int TestA.b                                   1
     16     1   boolean TestA.a                                   false
     17     7           (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 7 bytes external = 7 bytes total


主线程调用在加锁后--------
com.lry.thread.TestA object internals:
OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           ea 12 79 1c (11101010 00010010 01111001 00011100) (477696746)
      4     4           (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4           (object header)                           a0 c1 00 f8 (10100000 11000001 00000000 11111000) (-134168160)
     12     4       int TestA.b                                   1
     16     1   boolean TestA.a                                   false
     17     7           (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 7 bytes external = 7 bytes total


GC 方法启动--------
com.lry.thread.TestA object internals:
OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           09 00 00 00 (00001001 00000000 00000000 00000000) (9)
      4     4           (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4           (object header)                           a0 c1 00 f8 (10100000 11000001 00000000 11111000) (-134168160)
     12     4       int TestA.b                                   1
     16     1   boolean TestA.a                                   false
     17     7           (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 7 bytes external = 7 bytes total

很清晰的看到:

在jvm启动时,当没有其他线程来争抢资源时,为轻量锁,自定义线程加锁5秒,在1秒后,主线程取,发现现在资源被线程t加了锁,这时对象状态还未改变,当主线程sync调用a.方法后,发现a对象资源正在被线程t加锁中,所以这时候就出现资源竞争,变为重量级锁。

如果,我们在启动时加上-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0,那么一开始的打印结果就不会是无锁,而是偏向锁:101, 结果就不贴上了。

当我们调用wait方法后,偏向锁就会立即变为重量级锁。

我们修改代码:让加锁对象阻塞

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static void main(String[] args) {
    TestA a = new TestA();
    System.out.println("进入线程前。。。");
    System.out.println(ClassLayout.parseInstance(a).toPrintable());
    Thread t = new Thread(()->{
        synchronized (a) {
            System.out.println("进入 线程 方法内。。");
            System.out.println(ClassLayout.parseInstance(a).toPrintable());

            try {
                a.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程等待结束。。。");
            System.out.println(ClassLayout.parseInstance(a).toPrintable());


        }
    });

    t.start();

    try {
        TimeUnit.SECONDS.sleep(4);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("等待4秒后,主线程执行。。。");
    synchronized (a) {
        a.notifyAll();
        System.out.println(ClassLayout.parseInstance(a).toPrintable());
    }
}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
进入线程前。。。
com.lry.thread.TestA object internals:
OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)
      4     4           (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4           (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4       int TestA.b                                   0
     16     1   boolean TestA.a                                   false
     17     7           (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 7 bytes external = 7 bytes total

进入 线程 方法内。。
com.lry.thread.TestA object internals:
OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           05 68 ad 20 (00000101 01101000 10101101 00100000) (548235269)
      4     4           (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4           (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4       int TestA.b                                   0
     16     1   boolean TestA.a                                   false
     17     7           (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 7 bytes external = 7 bytes total

等待4秒后,主线程执行。。。
com.lry.thread.TestA object internals:
OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           8a 5d 4f 1d (10001010 01011101 01001111 00011101) (491740554)
      4     4           (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4           (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4       int TestA.b                                   0
     16     1   boolean TestA.a                                   false
     17     7           (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 7 bytes external = 7 bytes total

线程等待结束。。。
com.lry.thread.TestA object internals:
OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           8a 5d 4f 1d (10001010 01011101 01001111 00011101) (491740554)
      4     4           (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4           (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4       int TestA.b                                   0
     16     1   boolean TestA.a                                   false
     17     7           (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 7 bytes external = 7 bytes total

刚开始都是偏向锁,但有一点不同,就时第一行(对象头信息),状态后面的值不同个,第一个结果是0 ,第二个的结果是非0,可以理解为第一个0的是没有线程持有,而第二个加锁后,有线程持有,偏向于加锁的这个线程。在hashcode运算之前,这种可变的状态称为可偏向状态。

还有重要的一点时,当计算过hashcode后,就不能偏向了

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static void main(String[] args) {
    TestA a = new TestA();
    a.hashCode();
    synchronized (a) {
        System.out.println(ClassLayout.parseInstance(a).toPrintable());
    }
    System.out.println(ClassLayout.parseInstance(a).toPrintable());
}

没有计算前

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)
      4     4           (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)

计算后的

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           01 c2 54 9e (00000001 11000010 01010100 10011110) (-1638612479)
      4     4           (object header)                           0e 00 00 00 (00001110 00000000 00000000 00000000) (14)

轻量级锁执行后就变为了无锁。

偏向锁和轻量锁性能对比

-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static void main(String[] args) {
    TestA a = new TestA();
    long start = System.currentTimeMillis();
    for (int i = 0; i < 10000000L; i++) {
        synchronized (a) {
            a.count2();
        }
    }
    long endd = System.currentTimeMillis();
    System.out.println("用时:"+(endd - start));
}

轻量级锁和重量级锁对比

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static void main(String[] args) throws InterruptedException {
    TestA a = new TestA();
    long start = System.currentTimeMillis();
    CountDownLatch latch = new CountDownLatch(10000000);
    for (int i = 0; i < 2; i++) {
        new Thread(()->{
            while (latch.getCount() > 0) {
                synchronized (a) {
                    a.count2();
                    latch.countDown();
                }
            }
        }).start();
    }

    latch.await();

    long end = System.currentTimeMillis();
    System.out.println("用时:"+(end-start));
}

得到下面的数据,偏向锁的性能,可见他们的差距非常大。

偏向

轻量

重量

16

250

377

总结:

  1. 对象由:对象头、实例对象数据、对齐字节组成;
  2. 对象头占64位,但只有31位表示hashcode,对象头在windows上的表示要倒着看,然后最后一位表示对象的状态;
  3. 加锁就是改变对象头中的标志位
    1. 无锁 001
    2. 偏向锁 101
    3. 轻量级锁 000
    4. 重量级锁 010
  4. 计算过hashcode后,就不能再偏向了;
  5. 使用了wait方法,锁会立即变为重量级锁;
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020/04/06 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
多线程五 锁的膨胀过程
上一篇中,涉及到了锁升级的过程,也对其锁的升级有了一个大概的了解:单线程持有,在jvm延迟偏向的时间内是轻量级锁,之后为偏向锁,出现多个线程交替执行,对同一资源加锁会升级为轻量级锁,多个线程竞争拿不到锁会升级为重量级锁。在上一篇的基础上再进一步的了解锁升级的过程。
用针戳左手中指指头
2021/01/29
3250
线程并发带来的安全性问题 之 同步锁(二)
上一节我们学习了线程并发常见的安全性问题、锁的底层类型和对象结构的差异、锁升级相关知识。今天我们继续学习锁是如何升级的?
架构探险之道
2023/03/04
3670
线程并发带来的安全性问题 之 同步锁(二)
多线程基础(五):java对象的MarkWord及synchronized锁升级过程
在前面聊过了如何使用synchronized,以及synchronized不同的加锁方式分别锁的是哪些对象。本文对synchronized底层的原理进行深层次的分析。
冬天里的懒猫
2020/09/10
1K0
多线程基础(五):java对象的MarkWord及synchronized锁升级过程
线程并发带来的安全性问题 之 同步锁(一)
在下面的案例中,演示了两个线程分别去去调用 demo.incr 方法来对 i 这个变量进行叠加,预期结果 应该是20000,但是实际结果却是小于等于20000的值。
架构探险之道
2023/03/04
3070
线程并发带来的安全性问题 之 同步锁(一)
深入了解Java锁
继 打印Java对象头,我们再深入探索一下Java锁。无锁状态我们就不说了,下面我们一一打印偏向锁、轻量锁,重量锁的对象头。
@阿诚
2021/02/04
4920
偏向锁批量重偏向与批量撤销
批量重偏向:当一个线程创建了大量对象并执行了初始的同步操作,后来另一个线程也来将这些对象作为锁对象进行操作,会导偏向锁重偏向的操作。 批量撤销:在多线程竞争剧烈的情况下,使用偏向锁将会降低效率,于是乎产生了批量撤销机制。 JVM的默认参数值: intx BiasedLockingBulkRebiasThreshold = 20 默认偏向锁批量重偏向阈值 intx BiasedLockingBulkRevokeThreshold = 40 默认偏向锁批量撤销阈值 批量重偏向 测试代码: pu
@阿诚
2021/02/04
1.6K0
synchronized 底层如何实现?什么是锁升级、降级?
synchronized 代码块是由一对 monitorenter/monitorexit 指令实现的,Monitor 对象是同步的基本实现单元。
王小明_HIT
2020/05/08
1.5K0
synchronized 底层如何实现?什么是锁升级、降级?
【多线程与高并发】- 锁的机制与底层优化原理
最近经常研究一些关于线程并发的问题,再开发中也实实在在遇到过许多的并发问题,之前所学的是如何解决这些问题,然而接下来就得理解一下底层原理。
怒放吧德德
2024/09/06
3380
synchronized的实现原理——锁膨胀过程
上一篇分析了优化后的synchronized在不同场景下对象头中的表现形式,还记得那个结论吗?当一个线程第一次获取锁后再去拿锁就是偏向锁,如果有别的线程和当前线程交替执行就膨胀为轻量级锁,如果发生竞争就会膨胀为重量级锁。这句话看起来很简单,但实际上synhronized的膨胀过程是非常复杂的,有许多场景和细节需要考虑,本篇就对其进行详细分析。
夜勿语
2020/09/15
8770
面试专题:Synchronized 锁的升级过程(锁/对象状态)及底层原理
这个面试题其实涉及到的底层知识比较多,在Java中都知道synchronized,这是一个关键字,为什么使用了之后,可以结果多线程安全问题。里面内部流程是怎样的呢?加锁是加在哪里?金三银四越来越卷,面试官不再是,单纯的问如何解决线程安全,有没有使用过synchronized,而是想知道synchronized底层的知识点。本文就深入讲解synchronized底层原理,对象加锁是如果一步一步实现的。
小明爱吃火锅
2024/02/18
1.6K0
面试专题:Synchronized 锁的升级过程(锁/对象状态)及底层原理
JVM - 剖析Java对象头Object Header之对象大小
JVM - 写了这么多年代码,你知不道new对象背后的逻辑? 中大体介绍了Java中 new 对象背后的主要流程,其中对象头的部分,我们仅仅是点到为止,这里我们深入剖一下Object Header的奥秘 。
小小工匠
2021/08/17
1.7K0
synchronized底层实现知多少?synchronized加锁还会升级?
线程2将count减到了97,线程3、线程1在某一刻也做了count--,但是结果却也是97,说明他们在做count--的时候并不知道有别的线程也操作了count。
行百里er
2020/12/02
4880
synchronized底层实现知多少?synchronized加锁还会升级?
并发基石-Markword与锁升级
synchronized关键字是java提供的互斥锁关键字,我们常说的互斥锁一般都是非自旋锁,即竞争不到锁的线程会进入阻塞状态知道被唤醒 今天我们来讲讲java中用来对synchronized进行优化的三种锁,同时会介绍markword对象头 目前我在网上搜到的十几篇博客讲的都有问题,可能有写对的我没搜到. 很多人不经过验证直接把markOop.hpp中的JavaThread*当成ThreadId这是错误的,实际是java线程在C语言的指针 并且未计算过hashCode和计算过hashCode的情况也是不一样的 本篇博客最后会展示使用jol工具,读取展示对象头的结果进行验证 附上openjdk的markOop.hpp链接
歪歪梯
2020/06/19
6482
synchronized 锁的升级过程
测试代码 static A obj; // -XX:BiasedLockingStartupDelay=0 偏向锁开关 // -XX:+PrintFlagsInitial 打印所有参数 public static void main(String[] args) throws InterruptedException { obj = new A(); // Thread.sleep(60000); System.out.println(ClassLayout.parseInstance(ob
付威
2021/03/07
7060
对象实例化与内存布局(深入)
创建对象方式有:new、Class的newInstance()、Constructor的newInst(Xxx)、使用clone()、使用反序列化、第三方库Objenesis;
逍遥壮士
2021/05/24
1.3K0
对象实例化与内存布局(深入)
初步了解Java对象布局
其中byte、short、int、long都是表示整数的,只不过他们的取值范围不一样
iiopsd
2023/10/17
2540
初步了解Java对象布局
3分钟带你了解对象的创建过程
在之前的文章中,我们介绍了类加载的过程和 JVM 内存布局相关的知识。本篇我们综合之前的知识,结合代码一起推演一下对象的真实创建过程,以及对象创建完成之后在 JVM 中是如何保存的。
Java极客技术
2024/02/22
5060
3分钟带你了解对象的创建过程
JVM 系列(4)一看就懂的对象内存布局 审核中
Java 中一切皆对象,同时对象也是 Java 编程中接触最多的概念,深入理解 Java 对象能够更帮助我们深入地掌握 Java 技术栈。在这篇文章里,我们将从内存的视角,带你深入理解 Java 对象在虚拟机中的表现形式。
用户9995743
2022/09/26
5180
JVM 系列(4)一看就懂的对象内存布局 审核中
[JAVA基础] - JVM对象内存布局及锁的标记位
一、对象布局 1、对象头 1)存储对象自身的运行时数据 hash码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。占位32/64位虚拟机分别占32/64个比特,官方称"Mark Word" 2)类型指针 指向对象的元数据,如果是数组,还会存储数组长度。 2、实例数据 3、对齐填充 要求对象是8的整数倍,对象头已经是8位的整数倍,只填充实例数据即可。 二、Object o = new Object()内存占用情况 占用16个字节 对象头12个字节,对齐填充4个字
夹胡碰
2022/05/19
5070
[JAVA基础] - JVM对象内存布局及锁的标记位
JVM - 剖析Java对象头Object Header之指针压缩
同一个对象, 不开启指针压缩 8字节 存入堆中和 开启指针压缩4字节存入堆中,哪个更好一些,显而易见。
小小工匠
2021/08/17
1.1K0
推荐阅读
相关推荐
多线程五 锁的膨胀过程
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验