java多线程---synchronizedsynchronized分析

synchronized分析

使用方法

1、修饰方法

public synchronized void test(){
        
}

2、修饰对象(锁定的为什么一定高要是对象?)

public void test2(){
//Todo
    synchronized (this){
        
    }
    //todo
}
//由于是修饰对象,syn的范围由所修饰对象的生命周期来

锁是如何存储的

在之前的一篇ReentrantLock的分析中,可以看到,使用了一个线程对象来存错当前持有锁的线程,和一个state的锁标识。

这里也就可以推测,synchronized和lock应该也相似,也有这2个属性。如下:

线程a ->      对象o        <- 线程b 
           (持有锁的线程、
           锁的标识)

也就是说,在对象头中会存储,一个获锁的线程,和已经被强占锁的标识。
关于对象头,根据jvm中的介绍,一个对象在内存中的存错可以分为如下几个部分。

对象
对象标记
类元信息
实例数据
[对其填充]

对象头=对象标记+类元信息

下面为hotspot的一部分代码

enum { age_bits                 = 4,
         lock_bits                = 2,
         biased_lock_bits         = 1,
         max_hash_bits            = BitsPerWord - age_bits - lock_bits - biased_lock_bits,
         hash_bits                = max_hash_bits > 31 ? 31 : max_hash_bits,
         cms_bits                 = LP64_ONLY(1) NOT_LP64(0),
         epoch_bits               = 2
  };

可以看到有hashcode,分代年龄age,biased locl(偏向锁标识)等等.于是我们可以得到如下的表格(根据32位的hotspot)

锁状态 25bit 4bit 1bit(是否偏向锁) 2bit(锁标志位)
无锁 对象的hashcode 分代年龄 0 01
偏向锁 线程ID(23bit) + epoch(2bit) 分代年龄 1 01
轻量级锁 指向栈中锁记录的指针(30bit) 00
重量级锁 指向重量级锁记录的指针(30bit) 10
gc标记 空(30bit) 11

锁的状态

在1.6以后,synchronized做了一个优化(无锁->偏向锁->轻量级锁->重量级锁)也就是锁的升级,随着升级,性能消耗也就越大。

  1. 偏向锁:根据数据统计显示,大部分情况下,锁不存在竞争,都是由同一个线程获得。所以利用了23位来记录线程的ID, 所以当线程再次访问时,不需要获取锁,所以性能很快等于无锁的状态。
  2. 轻量级锁:当线程进行竞争锁的时候,会撤销偏向锁,升级为轻量级锁。轻量级为cas+自旋的方式获得。其中,jvm会记录一个lockrecord来记录锁的空间,在把对象头中的mark word 替换为指向锁记录的指针。再通过cas的方式去修改指向当前线程锁记录的指针。如果多次失败没有成功,则升级到重量级锁。
  3. 重量级锁:monitor 对象,获得一个objectmonitor。重量级锁的实现,都在这个对象中,一样也是通过cas去修改实现,但是失败会挂起线程。
上一篇:如何使用C#从位信息创建char数组


下一篇:190. Reverse Bits