java Synchronized的底层原理

CAS操作

AtomicInteger类中的所有原子性方法调用了sun.misc.Unsafe 类中的方法

Unsafe类中的方法通过自旋CAS实现,调用 native方法compareAndSwapInt

compareAndSwapInt作为native方法,由hotspot虚拟机的C++源码实现,最后到汇编码是一条

LOCK_IF_MP cmpxchg 指令,若为多核处理器则加锁,保障原子性

java对象的内存布局

通过JOL观察对象的内存布局

        Object o = new Object();
        System.out.println(ClassLayout.parseInstance(o).toPrintable());
        float[] array = new float[10];
        System.out.println(ClassLayout.parseInstance(array).toPrintable());

输出:

可以观察到,对象分为对象头,对齐空间,和对象实例数据,而对象头又分为8个字节的mark word和,4个字节的类型指针,在数组中还会存储一个数组长度。

Note:作为64位JVM的指针长度应该为8个字节,但是默认开启了UserCompressedClassPointers,

类型指针被压缩为4个字节。同时默认开启了UseCompressedOops,将对象指针也压缩为4个字节。

对象头

hotspot虚拟机对象头的实现

 

GC信息: 分代年龄:每次被垃圾回收一次分代年龄加1。4个bit,最大为15

hashcode

Synchronized关键字

死磕Synchronized底层实现--概论 · Issue #12 · farmerjohngit/myblog (github.com)

jdk源码剖析二: 对象内存布局、synchronized终极原理 - 只会一点java - 博客园 (cnblogs.com)

 

锁的升级过程: 无锁 - 偏向锁 - 轻量级锁 - 重量级锁

JVM默认有个偏向锁的时延开启,开启前默认实例化的对象为无锁态,不可偏向。锁标志位 001

时延过去后,实例化的对象为可偏向状态,偏向线程ID为0,Anonymous BiasedLock 

当第一个线程获取该对象的锁时,该对象中的Mark Word里的偏向线程指向该线程。该线程获得偏向锁。

Note:由于占用了Mark Word空间, 所以hashcode没有地方存储, 偏向锁在调用hashcode方法后会偏向撤销。

当第二个线程过去该对象的锁时,进行撤销偏向锁,升级为轻量级锁,原持有锁的线程内增加LockRecordId, Mark Word指向该LockRecordId。第二个线程开始适应自旋。

Note: 若epoch已过期(经过了批量重偏向),则重新获取偏向锁。 若经过了批量撤销,则跳过偏向锁逻辑,直接进入轻量级锁

第二个试图获取轻量锁的线程试图获取锁时。若已被占用,则进行锁升级;若无锁,复制markword, 进行cas操作,若失败,进行锁升级为重量级锁。

锁升级为重量级锁之后,进行CAS获取重量级锁,对象的MarkWord指向ObjectMonitor,把 ObjectMonitor 中的 _owner 设置为当前线程。通过 recursions 的自增来表示重入。

Note:线程进行锁升级后,进入enter(THREAD)方法, 然后cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL); 把Monitor的owner设置为自己,若失败则tryspin自适应自旋,再次失败后, 进入EnterI (THREAD) ;进入重量锁的获取逻辑

void ATTR ObjectMonitor::enter(TRAPS) {
   
  Thread * const Self = THREAD ;
  void * cur ;
  // owner为null代表无锁状态,如果能CAS设置成功,则当前线程直接获得锁
  cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL) ;
  if (cur == NULL) {
     ...
     return ;
  }
  // 如果是重入的情况
  if (cur == Self) {
     // TODO-FIXME: check for integer overflow!  BUGID 6557169.
     _recursions ++ ;
     return ;
  }
  // 当前线程是之前持有轻量级锁的线程。由轻量级锁膨胀且第一次调用enter方法,那cur是指向Lock Record的指针
  if (Self->is_lock_owned ((address)cur)) {
    assert (_recursions == 0, "internal state error");
    // 重入计数重置为1
    _recursions = 1 ;
    // 设置owner字段为当前线程(之前owner是指向Lock Record的指针)
    _owner = Self ;
    OwnerIsThread = 1 ;
    return ;
  }

  ...

  // 在调用系统的同步操作之前,先尝试自旋获得锁
  if (Knob_SpinEarly && TrySpin (Self) > 0) {
     ...
     //自旋的过程中获得了锁,则直接返回
     Self->_Stalled = 0 ;
     return ;
  }

  ...

  { 
    ...

    for (;;) {
      jt->set_suspend_equivalent();
      // 在该方法中调用系统同步操作
      EnterI (THREAD) ;
      ...
    }
    Self->set_current_pending_monitor(NULL);
    
  }

  ...

}

重量锁的获取要执行Enterl方法,

重量级锁的获取逻辑:

void ATTR ObjectMonitor::EnterI (TRAPS) {
    Thread * Self = THREAD ;
    ...
    // 尝试获得锁
    if (TryLock (Self) > 0) {
        ...
        return ;
    }

    DeferredInitialize () ;
 
	// 自旋
    if (TrySpin (Self) > 0) {
        ...
        return ;
    }
    
    ...
	
    // 将线程封装成node节点中
    ObjectWaiter node(Self) ;
    Self->_ParkEvent->reset() ;
    node._prev   = (ObjectWaiter *) 0xBAD ;
    node.TState  = ObjectWaiter::TS_CXQ ;

    // 将node节点插入到_cxq队列的头部,cxq是一个单向链表
    ObjectWaiter * nxt ;
    for (;;) {
        node._next = nxt = _cxq ;
        if (Atomic::cmpxchg_ptr (&node, &_cxq, nxt) == nxt) break ;

        // CAS失败的话 再尝试获得锁,这样可以降低插入到_cxq队列的频率
        if (TryLock (Self) > 0) {
            ...
            return ;
        }
    }

	// SyncFlags默认为0,如果没有其他等待的线程,则将_Responsible设置为自己
    if ((SyncFlags & 16) == 0 && nxt == NULL && _EntryList == NULL) {
        Atomic::cmpxchg_ptr (Self, &_Responsible, NULL) ;
    }


    TEVENT (Inflated enter - Contention) ;
    int nWakeups = 0 ;
    int RecheckInterval = 1 ;

    for (;;) {

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

        ...

        // park self
        if (_Responsible == Self || (SyncFlags & 1)) {
            // 当前线程是_Responsible时,调用的是带时间参数的park
            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 {
            //否则直接调用park挂起当前线程
            TEVENT (Inflated enter - park UNTIMED) ;
            Self->_ParkEvent->park() ;
        }

        if (TryLock(Self) > 0) break ;

        ...
        
        if ((Knob_SpinAfterFutile & 1) && TrySpin (Self) > 0) break ;

       	...
        // 在释放锁时,_succ会被设置为EntryList或_cxq中的一个线程
        if (_succ == Self) _succ = NULL ;

        // Invariant: after clearing _succ a thread *must* retry _owner before parking.
        OrderAccess::fence() ;
    }

   // 走到这里说明已经获得锁了

    assert (_owner == Self      , "invariant") ;
    assert (object() != NULL    , "invariant") ;
  
	// 将当前线程的node从cxq或EntryList中移除
    UnlinkAfterAcquire (Self, &node) ;
    if (_succ == Self) _succ = NULL ;
	if (_Responsible == Self) {
        _Responsible = NULL ;
        OrderAccess::fence();
    }
    ...
    return ;
}

在获取重量锁前先自旋获取锁,若失败则该线程封装成一个ObjectWaiter对象插入到cxq的队列的队首,然后调用park函数挂起当前线程。线程陷入阻塞状态。

Note:如果线程获得锁后调用Object#wait方法,则会将线程加入到WaitSet中,当被Object#notify唤醒后,会将线程从WaitSet移动到cxq或EntryList中去。需要注意的是,当调用一个锁对象的waitnotify方法时,如当前锁的状态是偏向锁或轻量级锁则会先膨胀成重量级锁

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Javasynchronized底层原理是基于对象监视器(Object Monitor)实现的。 每个Java对象都有一个与之关联的监视器锁(Monitor Lock),也称为内置锁(Intrinsic Lock)或互斥锁(Mutex Lock)。当一个线程试图进入一个synchronized方法或代码块时,它会尝试获取该方法或代码块所关联对象的监视器锁。 当一个线程成功获取锁后,它可以执行同步代码。其他线程如果想要获取同一个锁,就必须等待该线程释放锁。这样可以确保同一时间只有一个线程能够执行被锁定的代码,从而实现了线程安全。 底层实现中,监视器锁的获取和释放是通过底层操作系统的互斥原语来实现的。具体地说,当一个线程获取锁时,它会尝试将对象的监视器锁标记为已占用。如果锁已经被其他线程占用,那么获取锁的线程就会被阻塞,直到锁被释放。 在Java虚拟机中,对象监视器的实现通常包括一个等待队列(Waiting Queue)和一个通知队列(Notification Queue)。等待队列用于存放那些尝试获取锁但未成功的线程,而通知队列用于存放那些等待被唤醒的线程。 当一个线程释放锁时,它会将锁的状态设置为可用,并且从等待队列中选择一个线程进行唤醒,使其有机会获取锁。被唤醒的线程会进入到就绪状态,然后与其他线程竞争获取锁。 总之,Javasynchronized底层原理是通过对象监视器实现的,依赖于操作系统提供的互斥原语来实现线程同步和互斥访问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值