java synchronized锁升级过程

synchronized在jdk1.6之前,一直都是重量级锁。为什么称之为重量级锁呢,主要是因为锁的资源是通过操作系统去申请 ,所以比较重量级。

在jdk1.6之后,jvm对synchronized进行了升级,对锁的粒度进行了细化,分为无锁,偏向锁,轻量级锁,重量级锁。性能得到了很大的提升。

锁升级的过程是怎样的呢?

主要一开始是无锁的状态,如果偏向锁未启动,当有其他线程竞争时,会升级为轻量级锁,如果偏向锁启动,则会升级为偏向锁,如果竞争激烈则会从偏向锁升级为重量级锁,如果不激烈则有偏向锁升级为轻量级锁,再由轻量级锁升级为重量级锁。

过程主要如下,参考马士兵老师的锁升级过程

 

接下来我们就要验证下锁升级的过程。在验证锁升级的过程之前,先了解下java对象的结构,因为锁的信息就是放在对象信息的markword中。

锁状态25位31位1位4bit1bit
偏向锁位
2bit
锁标志
无锁unusedhashcode(如果有调用)unused分代年龄001
        
锁状态54位2位1位4bit1bit
偏向锁位
2bit
锁标志
偏向锁当前线程指针Epochunused分代年龄101
        
锁状态62位2bit
锁标志
轻量级锁指向线程栈中Lock Record的指针00
重量级锁指向互斥量(重量级锁)的指针10
GC标记信息CMS过程用到的标记信息11

根据markword的结构可以看到低位的2位分别表示锁的信息,第三位表示偏向锁的信息。

接下来我们来验证下锁升级的过程,这里需要借助下JOL这个工具,打印出对象的信息。在maven中添加依赖。

        <dependency>
            <groupId>org.openjdk.jol</groupId>
            <artifactId>jol-core</artifactId>
            <version>0.9</version>
        </dependency>

现在来验证下锁升级过程。

先创建一个对象和方法,然后再多线程调用MyObject这个对象信息,再打印出对象信息,看看锁升级的过程。

public class MyObject {
    public synchronized void test(){
        System.out.println("test");
    }
}



import org.openjdk.jol.info.ClassLayout;

import java.util.ArrayList;
import java.util.List;

public class TestSynchronizedUpgrade {

    public static void main(String[] args) throws InterruptedException {
        MyObject o = new MyObject();
        List<Thread> threads = new ArrayList<>();
        for (int i = 0; i < 20; i++) {
            Thread thread = new Thread(() -> o.test());
            threads.add(thread);
        }

        for (Thread thread : threads) {
            thread.start();
            // ClassLayout输出对象信息
            System.out.println(ClassLayout.parseInstance(o).toPrintable());
        }


    }
}

 

运行输出为:

test.jol.MyObject 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)                           92 c3 00 20 (10010010 11000011 00000000 00100000) (536920978)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

test.jol.MyObject 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)                           92 c3 00 20 (10010010 11000011 00000000 00100000) (536920978)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

test.jol.MyObject 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)                           92 c3 00 20 (10010010 11000011 00000000 00100000) (536920978)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

test.jol.MyObject 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)                           92 c3 00 20 (10010010 11000011 00000000 00100000) (536920978)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

test
test
test
test
test.jol.MyObject object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           88 ef 3c 1c (10001000 11101111 00111100 00011100) (473755528)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           92 c3 00 20 (10010010 11000011 00000000 00100000) (536920978)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

test
test.jol.MyObject object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           8a 36 88 18 (10001010 00110110 10001000 00011000) (411580042)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           92 c3 00 20 (10010010 11000011 00000000 00100000) (536920978)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

 

我们先挑出来一个看,看到第三行的数据,前四个字节的值为01 00 00 00 这是16进制数据后面括号中 (00000001 00000000 00000000 00000000)表示的二进制数据,也就是bit的数据,也就是低位三个数据是001,对照上面markword的值可以知道,开始的时候是无锁的状态。在运行到一段时间之后,低位会变成00也就是轻量级锁,再后面会变成10,也就是升级成了重量级锁。

那为什么没有偏向锁呢?查了资料发现其实是jdk在开始时偏向锁是不启动的,可能是在开始运行是并没有什么锁的竞争,所以偏向锁没有启动,当运行一段时间之后才会启动,大概是4秒之后才会启动偏向锁。

但是我在测试偏向锁的时候,发现会偏向锁变成无锁,再变成轻量级锁,再变成无锁,这点有点不太能理解,如果有人明白告知下,不胜感激!

 

Java中的synchronized锁升级是为了提高多线程并发执行的性能和效率。它通过在锁的使用过程中进行优化和升级来实现。 在Java中,synchronized锁的升级主要涉及三个层面:Java层面、字节码层面和JVM层面(对象头)。 在Java层面上,synchronized锁的升级包括以下几种状态: 1. 无锁状态:多个线程可以同时进入临界区,没有互斥的限制; 2. 偏向锁状态:当只有一个线程访问临界区时,偏向锁可以减少锁的竞争; 3. 轻量级锁状态:多个线程竞争同一个锁时,锁会升级为轻量级锁,通过CAS操作来实现快速的加锁和解锁; 4. 重量级锁状态:多个线程竞争同一个锁时,锁会升级为重量级锁,使用操作系统的互斥量来实现线程的阻塞和唤醒[1]。 在字节码层面上,synchronized同步代码块的锁升级实际上是通过字节码指令来实现的。当进入同步代码块时,会通过monitorenter指令获取锁,在退出同步代码块时,会通过monitorexit指令释放锁。这些指令可以保证临界区的原子性和互斥性,从而实现线程的同步。 在JVM层面上,synchronized锁的升级是通过对象头中的标记位来实现的。对象头中的标记位包括了锁标志位、线程ID和指向锁记录的指针。通过这些标记位,JVM可以判断锁的状态和竞争情况,从而进行锁的升级和降级。 总结来说,Java中的synchronized锁升级是为了提高多线程并发执行的性能和效率。它通过在不同层面上对锁进行优化和升级来实现线程的同步和互斥,从而保证临界区的原子性和正确性。这些优化和升级包括了无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值