Synchronized学习

237 阅读11分钟

Synchronized

锁代码块和方法有什么区别?

image.png

Sync锁方法是隐式的,无需同通过字节码指令来控制,它实现在方法调用和返回操作中。虚拟机可以从方法常量池中的方法表结构ACC_SYNCHRONIZED访问标志得知一个方法是否被声明为同步方法。当调用时,调用指令检查该方法是否被设置了ACC_SYNCHRONIZED标志,如果设置了,执行线程就要求先成功持有管程,然后才能执行方法,最后当方法完成时释放管程。

在方法执行期间,执行线程先拥有了管程,其他任何线程都不能再获得到管程。如果一个同步方法执行期间抛出了异常,并且在方法内部无法处理此异常,那这个同步方法所持有的管程将在异常抛到同步方法边界之外时自动释放。

同步代码块是通过获得对象的Monitor对象后用monitorenter和两条monitorexit指令来完成的。

在Java虚拟机中,每个对象和类在逻辑上与管程相关联。为了实现管程的互斥能力,一个锁(有时也称为一个互斥锁)关联每个对象和类。这就是所谓操作系统书籍上的信号量(semaphore),互斥锁是一个二进制信号量。 如果一个线程拥有一些数据的锁,那么没有其他线程可以获取这个锁,直到拥有锁的线程释放它。当我们做多线程编程时,如果任何时候都需要编写一个信号量,这将是不方便。幸运的是,我们并不需要,因为JVM自动为我们实现了。 声明一个管程区域,这意味着数据不能被超过一个线程访问,java提供同步代码块和同步方法。一旦代码被嵌入synchronized关键字,它就是一个管程区域。该锁在后台通过JVM自动实现。

Java并发基石

image.png

Blocked和Wait有什么区别

判断是否是BLOCKED可以看该线程是否在等待获取锁,Object.wait()、 Thread.join()、 LockSupport.park()会使得线程进入WAITING状态。对应的singal()、notify()、notifyAll()、unpark()

sleep和wait有什么区别

1、来自不同的类:sleep是Thread的静态类方法,谁调用的谁去睡觉,即使在a线程里调用了b的sleep方法,实际上还是a去睡觉,要让b线程睡觉要在b的代码中调用sleep。

2、有没有释放锁(释放资源):sleep不出让系统资源;wait是进入线程等待池等待,出让系统资源,其他线程可以占用CPU。

3、一般wait不会加时间限制,因为如果wait线程的运行资源不够,再出来也没用,要等待其他线程调用notify/notifyAll唤醒等待池中的所有线程,才会进入就绪队列等待OS分配系统资源。sleep(milliseconds)可以用时间指定使它自动唤醒过来,如果时间不到只能调用interrupt()强行打断。

4、sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常。

Object Monitor机制

image.png

通常情况下,我们在代码中使用synchronized关键字协调多个线程的工作过程,实际上就是使用Object Monitor控制对多个线程的工作过程进行协调。synchronized关键字的执行过程从传统的理解上就是“悲观锁”设计思想的一种实现。但实际上synchronized关键字的执行过程还涉及到锁机制的升级过程,升级顺序为 自旋锁、偏向锁、轻量级锁、重量级锁

自旋锁

自旋锁:自旋锁实际上是一种基于CAS原理的实现方式(关于CAS原理在本专题之前的文章中已经介绍过,从根本上来说这是一个“乐观锁”设计思想的具体实现)。自旋锁就是在满足一定条件下,让当前还没有获得对象操作权的线程进入一种“自循环”的等待状态,而不是真正让这个线程释放CPU资源。这个等待状态的尝试时间和自旋的次数非常短,如果在这个非常短的时间内该对象还没有获得对象操作权,则锁状态就会升级。

自旋锁的特点是,由于等待线程并没有切换出CPU资源,而是使用“自循环”的方式将自己保持在CPU L1/L2缓存中,这样就避免了线程在CPU的切换过程,在实际上并没有什么并发量的执行环境下减少了程序的处理时间。当基于当前对象的synchronized控制还处于“自旋锁”状态时,实际上并没有真正开启Object Monitor控制机制,所以这个自旋锁状态(包括偏向锁、轻量级锁)并不会反映在对象头的数据结构中。

空转CPU不让系统调用这个过程发生。

偏向锁

线程获得偏向锁将会在同步对象的MarkWord中写入自己的线程ID,这样当其访问同步资源的时候就无需任何操作了,其对应一种这个同步对象长时间只有一个线程使用的情况。

如果出现了另外一个线程尝试获得偏向锁,自旋等待之后,就会升级为轻量级锁。

轻量级锁

轻量级锁:按照之前对偏向锁的描述,偏向锁主要解决在没有对象抢占的情况下,由单个线程进度同步块时的加锁问题。一旦出现了两个或多个线程抢占对象操作时,偏向锁就会升级为轻量级锁。轻量级锁同样使用CAS技术进行实现,它主要说的是多个需要抢占对象操作权的线程,通过CAS的是实现技术持续尝试获得对象的操作权的过程。 按照轻量级锁的定义,我们将以上的栗子继续下去。当前对象Y的锁级别升级为轻量级锁后,JVM将在线程A、线程B和之后请求获得对象Y操作的若干线程的当前栈帧中,添加一个锁记录空间(记为Key空间),并将对象头中的Mark Word复制到锁记录中。然后线程会持续尝试使用CAS原理将对象头中的Mark Word部分替换为指向本线程锁记录空间的指针。如果替换成功则当前线程获得这个对象的操作权;如果多次CAS持续失败,说明当前对象的多线程抢占现象很严重,这是对象锁升级为重量锁状态,并使用操作系统层面的Mutex Lock(互斥锁)技术进行实现。

image.png

锁升级流程

v2-9db4211af1be81785f6cc51a58ae6054_r.jpg

偏向锁的状态下

偏向锁标志位(0\1)

检查同步对象对象头中的MarkWord记录的是否是当前线程ID。

CAS具体操作了哪些内容

是,想要替换同步对象对象头中MarkWord,失败,开始锁撤销流程,达到安全点暂停所有持有偏向锁的进程,检查原持有锁的线程状态,如果已经退出同步代码块或者说未活动状态,将mark word偏向锁标志改为0,唤醒原持有偏向锁的线程。

image.png

轻量级锁

image.png 加锁就是把同步资源对象的MarkWord存入到自己的栈中,然后MarkWord指向LockRecord,重入再再存入一个null值。

栈将自己的LockRecord替换回markWord的时候,如果失败就膨胀为重量级的锁。

image.png image.png

重量级锁

synchronized 底层是利用 monitor 对象,CAS 和 mutex 互斥锁来实现的,内部会有等待队列(cxq 和 EntryList)和条件等待队列(waitSet)来存放相应阻塞的线程。

image.png

monitorEnter指令执行的代码

bool ObjectMonitor::try_enter(Thread* THREAD) {
 if (THREAD != _owner) {
   if (THREAD->is_lock_owned ((address)_owner)) {
      assert(_recursions == 0, "internal state error");
      _owner = THREAD ;
      _recursions = 1 ;
      OwnerIsThread = 1 ;
      return true;
   }
   if (Atomic::cmpxchg_ptr (THREAD, &_owner, NULL) != NULL) {
     return false;
   }
   return true;
 } else {
   _recursions++;
   return true;
 }
}
ObjectMonitor::enter(TRAPS) {
    cur = Atomic::cmpxchg_ptr(Self,&_owner,NULL);
    if(cur == null) {
    return 
    }
    //
    if(cur == self) {
     _recursions ++;
    }
    
    if(self->is_lock_owned((address)cur)){
     _rescursion++;
     _owner = Self;
     OwnerIsThread = 1;
     return;
    }
}
inline intptr_t Atomic::cmpxchg_ptr(intptr_t exchange_value, volatile intptr_t* dest, intptr_t compare_value) {
 return (intptr_t)cmpxchg((jint)exchange_value, (volatile jint*)dest, (jint)compare_value);
}
inline jlong    Atomic::cmpxchg (jlong    exchange_value, volatile jlong*    dest, jlong    compare_value) {
 __asm {
   push ebx
   push edi
   mov eax, cmp_lo
   mov edx, cmp_hi
   mov edi, dest
   mov ebx, ex_lo
   mov ecx, ex_hi
 LOCK_IF_MP(mp)
   cmpxchg8b qword ptr [edi]
   pop edi
   pop ebx
 }
}

Atomic::cmpxchg_ptr成功就等于获得到了锁,CAS失败进入下一个循环

for(;;) {
  EnterI(Thread);
}

image.png

image.png

void ATTR ObjectMonitor::EnterI (TRAPS) {
    Thread * Self = THREAD ;
    assert (Self->is_Java_thread(), "invariant") ;
    //校验线程状态已经处于阻塞中
    assert (((JavaThread *) Self)->thread_state() == _thread_blocked   , "invariant") ;
 
    if (TryLock (Self) > 0) {
        //尝试获取锁,获取成功则返回
        assert (_succ != Self              , "invariant") ;
        assert (_owner == Self             , "invariant") ;
        assert (_Responsible != Self       , "invariant") ;
        return ;
    }
    
    //初始化自旋相关配置参数
    DeferredInitialize () ;
 
    if (TrySpin (Self) > 0) {
        //再次尝试自旋,获取锁成功则返回
        assert (_owner == Self        , "invariant") ;
        assert (_succ != Self         , "invariant") ;
        assert (_Responsible != Self  , "invariant") ;
        return ;
    }
 
    //自旋获取锁失败,将当前线程加入到等待队列中并且park
    assert (_succ  != Self            , "invariant") ;
    assert (_owner != Self            , "invariant") ;
    assert (_Responsible != Self      , "invariant") ;
    
 
    //创建一个ObjectWaiter并初始化
    ObjectWaiter node(Self) ;
    Self->_ParkEvent->reset() ;
    node._prev   = (ObjectWaiter *) 0xBAD ;
    node.TState  = ObjectWaiter::TS_CXQ ;
 
    ObjectWaiter * nxt ;
    for (;;) {
        node._next = nxt = _cxq ;
        //原子的修改_cxq为node,如果修改成功则终止循环,表示已经成功加入到链表中
        if (Atomic::cmpxchg_ptr (&node, &_cxq, nxt) == nxt) break ;
 
        //修改失败,某个线程改变了cxq
        if (TryLock (Self) > 0) {
            //再次尝试获取锁,获取成功则返回
            assert (_succ != Self         , "invariant") ;
            assert (_owner == Self        , "invariant") ;
            assert (_Responsible != Self  , "invariant") ;
            return ;
        }
    }
    
    //SyncFlags对的默认值是0
    if ((SyncFlags & 16) == 0 && nxt == NULL && _EntryList == NULL) {
        //nxt或者_EntryList为NULL,说明当前线程是第一个阻塞的线程,将_Responsible原子的修改为当前线程
        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") ;
 
        if ((SyncFlags & 2) && _Responsible == NULL) {
           //原子的将_Responsible置为Self
           Atomic::cmpxchg_ptr (Self, &_Responsible, NULL) ;
        }
 
        //将目标线程park掉,底层通过操作系统的互斥量实现,让当前线程休眠
        if (_Responsible == Self || (SyncFlags & 1)) {
            TEVENT (Inflated enter - park TIMED) ;
            Self->_ParkEvent->park ((jlong) RecheckInterval) ;
            //增加等待时间,最大不超过1s
            RecheckInterval *= 8 ;
            if (RecheckInterval > 1000) RecheckInterval = 1000 ;
        } else {
            TEVENT (Inflated enter - park UNTIMED) ;
            Self->_ParkEvent->park() ;
        }
        
        //线程被唤醒了,即某个占用锁的线程释放了锁,尝试抢占该锁
        if (TryLock(Self) > 0) break ;
 
 
        TEVENT (Inflated enter - Futile wakeup) ;
        if (ObjectMonitor::_sync_FutileWakeups != NULL) {
           //增加计数
           ObjectMonitor::_sync_FutileWakeups->inc() ;
        }
        //增加计数
        ++ nWakeups ;
 
        //Knob_SpinAfterFutile默认值是1,此时会再次尝试自旋获取锁
        if ((Knob_SpinAfterFutile & 1) && TrySpin (Self) > 0) break ;
        
        //Knob_ResetEvent默认值是0
        if ((Knob_ResetEvent & 1) && Self->_ParkEvent->fired()) {
           Self->_ParkEvent->reset() ;
           OrderAccess::fence() ;
        }
        if (_succ == Self) _succ = NULL ;
 
        //强制所有修改立即生效
        OrderAccess::fence() ;
    }
 
    //for循环结束,当前线程已经获取了锁
    assert (_owner == Self      , "invariant") ;
    assert (object() != NULL    , "invariant") ;
    
    //将其从EntryList或者cxq链表中移除
    UnlinkAfterAcquire (Self, &node) ;
    if (_succ == Self) _succ = NULL ;
 
    assert (_succ != Self, "invariant") ;
    if (_Responsible == Self) {
        //将_Responsible置为NULL
        _Responsible = NULL ;
        OrderAccess::fence(); // Dekker pivot-point
    }
 
    if (SyncFlags & 8) {
       OrderAccess::fence() ;
    }
    return ;
}
 
void ObjectMonitor::DeferredInitialize () {
//初始化完成时会将InitDone置为1,即只初始化第一次即可
  if (InitDone > 0) return ;
  if (Atomic::cmpxchg (-1, &InitDone, 0) != 0) {
      //将其原子的修改为-1,如果修改失败说明有一个线程已经完成了修改
      //自旋等待该线程完成初始化
      while (InitDone != 1) ;
      return ;
  }
 
  // SyncKnobs是一个配置项,用来配置跟自旋等待相关的属性
  if (SyncKnobs == NULL) SyncKnobs = "" ;
  
  //获取其字符长度
  size_t sz = strlen (SyncKnobs) ;
  //分配一个字符数组
  char * knobs = (char *) malloc (sz + 2) ;
  if (knobs == NULL) {
     //分配失败抛出异常
     vm_exit_out_of_memory (sz + 2, OOM_MALLOC_ERROR, "Parse SyncKnobs") ;
     guarantee (0, "invariant") ;
  }
  //复制到knobs
  strcpy (knobs, SyncKnobs) ;
  //加1的字符置为0,表示字符串结束
  knobs[sz+1] = 0 ;
  for (char * p = knobs ; *p ; p++) {
     if (*p == ':') *p = 0 ;
  }
  
  //初始化各项配置,kvGetInt负责查找配置项的值
  #define SETKNOB(x) { Knob_##x = kvGetInt (knobs, #x, Knob_##x); }
  SETKNOB(ReportSettings) ;
  SETKNOB(Verbose) ;
  SETKNOB(FixedSpin) ;
  SETKNOB(SpinLimit) ;
  SETKNOB(SpinBase) ;
  SETKNOB(SpinBackOff);
  SETKNOB(CASPenalty) ;
  SETKNOB(OXPenalty) ;
  SETKNOB(LogSpins) ;
  SETKNOB(SpinSetSucc) ;
  SETKNOB(SuccEnabled) ;
  SETKNOB(SuccRestrict) ;
  SETKNOB(Penalty) ;
  SETKNOB(Bonus) ;
  SETKNOB(BonusB) ;
  SETKNOB(Poverty) ;
  SETKNOB(SpinAfterFutile) ;
  SETKNOB(UsePause) ;
  SETKNOB(SpinEarly) ;
  SETKNOB(OState) ;
  SETKNOB(MaxSpinners) ;
  SETKNOB(PreSpin) ;
  SETKNOB(ExitPolicy) ;
  SETKNOB(QMode);
  SETKNOB(ResetEvent) ;
  SETKNOB(MoveNotifyee) ;
  SETKNOB(FastHSSEC) ;
  #undef SETKNOB
 
  if (Knob_Verbose) {
    //检查配置的合法性
    sanity_checks();
  }
 
  if (os::is_MP()) {
     BackOffMask = (1 << Knob_SpinBackOff) - 1 ;
     if (Knob_ReportSettings) ::printf ("BackOffMask=%X\n", BackOffMask) ;
  } else {
     Knob_SpinLimit = 0 ;
     Knob_SpinBase  = 0 ;
     Knob_PreSpin   = 0 ;
     Knob_FixedSpin = -1 ;
  }
 
  if (Knob_LogSpins == 0) {
     ObjectMonitor::_sync_FailedSpins = NULL ;
  }
  //释放knobs的内存
  free (knobs) ;
  //让修改立即生效
  OrderAccess::fence() ;
  //标识初始化完成
  InitDone = 1 ;
}
 
 
void ObjectMonitor::UnlinkAfterAcquire (Thread * Self, ObjectWaiter * SelfNode)
{
    assert (_owner == Self, "invariant") ;
    assert (SelfNode->_thread == Self, "invariant") ;
 
    if (SelfNode->TState == ObjectWaiter::TS_ENTER) {
        //正常情况走此分支,将SelfNode从_EntryList中移除
        //默认配置下,cxq链表中的节点会被转移到EntryList链表中,状态就置为TS_ENTER
        ObjectWaiter * nxt = SelfNode->_next ;
        ObjectWaiter * prv = SelfNode->_prev ;
        if (nxt != NULL) nxt->_prev = prv ;
        if (prv != NULL) prv->_next = nxt ;
        if (SelfNode == _EntryList ) _EntryList = nxt ;
        assert (nxt == NULL || nxt->TState == ObjectWaiter::TS_ENTER, "invariant") ;
        assert (prv == NULL || prv->TState == ObjectWaiter::TS_ENTER, "invariant") ;
        TEVENT (Unlink from EntryList) ;
    } else {
        guarantee (SelfNode->TState == ObjectWaiter::TS_CXQ, "invariant") ;
    
        ObjectWaiter * v = _cxq ;
        assert (v != NULL, "invariant") ;
        //如果v不等于SelfNode直接进入下面的分支,如果等于执行后面的CAS逻辑,将_cxq修改为next,如果修改失败会进入if分支
        if (v != SelfNode || Atomic::cmpxchg_ptr (SelfNode->_next, &_cxq, v) != v) {
            if (v == SelfNode) {
                assert (_cxq != v, "invariant") ;
                //修改失败,说明有其他线程修改了cxq,这里重新获取cxq
                v = _cxq ;          // CAS above failed - start scan at head of list
            }
            ObjectWaiter * p ;
            ObjectWaiter * q = NULL ;
            //遍历找到SelfNode,将其移除
            for (p = v ; p != NULL && p != SelfNode; p = p->_next) {
                q = p ;
                assert (p->TState == ObjectWaiter::TS_CXQ, "invariant") ;
            }
            assert (v != SelfNode,  "invariant") ;
            assert (p == SelfNode,  "Node not found on cxq") ;
            assert (p != _cxq,      "invariant") ;
            assert (q != NULL,      "invariant") ;
            assert (q->_next == p,  "invariant") ;
            q->_next = p->_next ;
        }
        TEVENT (Unlink from cxq) ;
    }
 
    //prev和next属性置为null
    SelfNode->_prev  = (ObjectWaiter *) 0xBAD ;
    SelfNode->_next  = (ObjectWaiter *) 0xBAD ;
    SelfNode->TState = ObjectWaiter::TS_RUN ;
}

1. 再次尝试CAS
2. 自适应自旋挣扎一下
3. 包装为ObjectWaiter对象加入到_cxq这个单向链表中
4. 入队之前再次尝试
//pthread_mutex_lock
5. 再不行就用Self->_ParkEvent->Park()阻塞

image.png

加锁

ObbjectMonitor::enter cmpxchg_ptr

cmpxchg_ptr就是获得ObjectMonitor中的_owner的操作,每次都是根据内存地址偏移量用当前线程的ID替换掉NULL值

lieshashike.png

解锁

ObbjectMonitor::exit _owner = NULL

image.png

进入_waitSet的情况下wait操作释放掉锁,剥夺系统资源重新进入_cxq队列和_EntrySet进行争夺锁操作

image.png