Synchronized

本篇文章将围绕synchronized关键字,应用大量图片、案例深入浅出的形容CAS、synchronized Java层面和C++层面的实现、锁降级的原理、源码等

大略观看工夫17分钟

能够带着几个问题去查看本文,如果认真看完,问题都会迎刃而解:

1、synchronized是怎么应用的?在Java层面是如何实现?

2、CAS是什么?能带来什么益处?又有什么毛病?

3、mark word是什么?跟synchronized有啥关系?

4、synchronized的锁降级优化是什么?在C++层面如何实现?

5、JDK 8 中轻量级锁CAS失败到底会不会自旋?

6、什么是object monitor?wait/notify办法是如何实现的?应用synchronized时,线程阻塞后是如何在阻塞队列中排序的?

...

synchronized Java层面实现

synchronized作用在代码块或办法上,用于保障并发环境下的同步机制

任何线程遇到synchronized都要先获取到锁能力执行代码块或办法中的操作

在Java中每个对象有一个对应的monitor对象(监视器),当获取到A对象的锁时,A对象的监视器对象中有个字段会指向以后线程,示意这个线程获取到A对象的锁(具体原理后文形容)

synchronized能够作用于一般对象和动态对象,当作用于动态对象、静态方法时,都是去获取其对应的Class对象的锁

synchronized作用在代码块上时,会应用monitorentry和monitorexit字节码指令来标识加锁、解锁

synchronized作用在办法上时,会在拜访标识上加上synchronized

指令中可能呈现两个monitorexit指令是因为当产生异样时,会主动执行monitorexit进行解锁

失常流程是PC 12-14,如果在此期间出现异常就会跳转到PC 17,最终在19执行monitorexit进行解锁

        Object obj = new Object();        synchronized (obj) {        }

在上篇文章中咱们说过原子性、可见性以及有序性

synchronized加锁解锁的字节码指令应用屏障,加锁时共享内存从主内存中从新读取,解锁前把工作内存数据写回主内存以此来保障可见性

因为获取到锁能力执行相当于串行执行,也就保障原子性和有序性,须要留神的是加锁与解锁之间的指令还是能够重排序的

CAS

为了更好的阐明synchronized原理和锁降级,咱们先来聊聊CAS

在上篇文章中咱们说过,volatile不能保障复合操作的原子性,应用synchronized办法或者CAS可能保障复合操作原子性

那什么是CAS呢?

CAS全称 Compare And Swap 比拟并替换,读取数据后要批改时用读取的数据和地址上的值进行比拟,如果相等那就将地址上的值替换为目标值,如果不相等,通常会从新读取数据再进行CAS操作,也就是失败重试

synchronized加锁是一种乐观策略,每次遇到时都认为会有并发问题,要先获取锁才操作

而CAS是一种乐观策略,每次先大胆的去操作,操作失败(CAS失败)再应用弥补措施(失败重试)

CAS与失败重试(循环)的组合形成乐观锁或者说自旋锁(循环尝试很像在自我旋转)

并发包下的原子类,依附Unsafe大量应用CAS操作,比方AtomicInteger的自增

    public final int getAndIncrement() {        return unsafe.getAndAddInt(this, valueOffset, 1);    }    //var1是调用办法的对象,var2是须要读取/批改的值在这个对象上的偏移量,var4是自增1    public final int getAndAddInt(Object var1, long var2, int var4) {        int var5;        do {            //var5是通过对象和字段偏移量获取到字段最新值            var5 = this.getIntVolatile(var1, var2);            //cas:var1,var2找到字段的值 与 var5比拟,相等就替换为 var5+var4         } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));        return var5;    }

CAS只能对一个变量进行操作,如果要对多个变量进行操作,那么只能对外封装一层(将多个变量封装为新对象的字段),再应用原子类中的AtomicReference

不知各位同学有没有发现,CAS的流程有个bug,就是在读数据与比拟数据之间,如果数据从A被扭转到B,再扭转到A,那么CAS也能执行胜利

这种场景有的业务可能承受,有的业务无奈承受,这就是所谓的ABA问题

而解决ABA问题的形式比较简单,能够再比拟时附加一个自增的版本号,JDK也提供解决ABA问题的原子类AtomicStampedReference

CAS可能防止线程阻塞,但如果始终失败就会始终循环,减少CPU的开销,CAS失败后重试的次数/时长不好评估

因而CAS操作实用于竞争小的场景,用CPU空转的开销来换取线程阻塞挂起/复原的开销

锁降级

晚期版本的synchronized会将获取不到锁的线程间接挂起,性能不好

JDK 6 时对synchronized的实现进行优化,也就是锁降级

锁的状态能够分为无锁、偏差锁、轻量级锁、重量级锁

能够临时把重量级锁了解为晚期获取不到锁就让线程挂起,新的优化也就是轻量级锁和偏差锁

mark word

为了更好的阐明锁降级,咱们先来聊聊Java对象头中的mark word

咱们上面的探索都是围绕64位的虚拟机

Java对象的内存由mark word、klass word、如果是数组还要记录长度、实例数据(字段)、对其填充(填充到8字节倍数)组成

mark word会记录锁状态,在不同锁状态的状况下记录的数据也不同

上面这个表格是从无锁到重量级锁mark word记录的内容

|----------------------------------------------------------------------|--------|--------|| unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:1  | lock:2 | 无锁   |----------------------------------------------------------------------|--------|--------||  thread:54 |         epoch:2      | unused:1 | age:4 | biased_lock:1 | lock:2 | 偏差锁|----------------------------------------------------------------------|--------|--------||                     ptr_to_lock_record:62                            | lock:2 | 轻量级锁|----------------------------------------------------------------------|--------|--------||                     ptr_to_heavyweight_monitor:62                    | lock:2 | 重量级锁|----------------------------------------------------------------------|--------|--------|

unused 示意还没应用

identity_hashcode 用于记录一致性哈希

age 用于记录GC年龄

biased_lock 标识是否应用偏差锁,0示意未开启,1示意开启

lock 用于标识锁状态标记位,01无锁或偏差锁、00轻量级锁、10重量级锁

thread 用于标识偏差的线程

epoch 记录偏差的工夫戳

ptr_to_lock_record 记录栈帧中的锁记录(后文介绍)

ptr_to_heavyweight_monitor 记录获取重量级锁的线程

jol查看mark word

比拟相熟mark word的同学能够跳过

理解mark word后再来相熟下不同锁状态下的mark word,我应用的是jol查看内存

       <!-- https://mvnrepository.com/artifact/org.openjdk.jol/jol-core -->        <dependency>            <groupId>org.openjdk.jol</groupId>            <artifactId>jol-core</artifactId>            <version>0.12</version>        </dependency>
无锁

各位同学试验时的mark word可能和我正文中的不同,咱们次要查看锁标识的值和是否启用偏差锁

    public void noLock() {        Object obj = new Object();        //mark word  00000001 被unused:1,age:4,biased_lock:1,lock:2应用,001示意0未启用偏差锁,01示意无锁        //01 00 00 00  (00000001 00000000 00000000 00000000)        //00 00 00 00  (00000000 00000000 00000000 00000000)        ClassLayout objClassLayout = ClassLayout.parseInstance(obj);        System.out.println(objClassLayout.toPrintable());        //计算一致性哈希后        //01 b6 ce a8        //6a 00 00 00        obj.hashCode();        System.out.println(objClassLayout.toPrintable());        //进行GC 查看GC年龄 0 0001 0 01 前2位示意锁状态01无锁,第三位biased_lock为0示意未启用偏差锁,后续四位则是GC年龄age 1        //09 b6 ce a8 (00001001 10110110 11001110 10101000)        //6a 00 00 00 (01101010 00000000 00000000 00000000)        System.gc();        System.out.println(objClassLayout.toPrintable());    }
轻量级锁
    public void lightLockTest() throws InterruptedException {        Object obj = new Object();        ClassLayout objClassLayout = ClassLayout.parseInstance(obj);        //1334729950        System.out.println(obj.hashCode());        //0 01 无锁        //01 4e c0 d5 (00000001 01001110 11000000 11010101)        //6a 00 00 00 (01101010 00000000 00000000 00000000)        System.out.println(Thread.currentThread().getName() + ":");        System.out.println(objClassLayout.toPrintable());        Thread thread1 = new Thread(() -> {            synchronized (obj) {                // 110110 00 中的00示意轻量级锁其余62位指向领有锁的线程                //d8 f1 5f 1d (11011000 11110001 01011111 00011101)                //00 00 00 00 (00000000 00000000 00000000 00000000)                System.out.println(Thread.currentThread().getName() + ":");                System.out.println(objClassLayout.toPrintable());                //1334729950                //无锁升级成轻量级锁后 hashcode未变 对象头中没存储hashcode 只存储领有锁的线程                //(实际上mark word内容被存储到lock record中,所以hashcode也被存储到lock record中)                System.out.println(obj.hashCode());            }        }, "t1");        thread1.start();        //期待t1执行完 防止 产生竞争        thread1.join();        //轻量级锁 开释后 mark word 复原成无锁 存储哈希code的状态        //01 4e c0 d5 (00000001 01001110 11000000 11010101)        //6a 00 00 00 (01101010 00000000 00000000 00000000)        System.out.println(Thread.currentThread().getName() + ":");        System.out.println(objClassLayout.toPrintable());        Thread thread2 = new Thread(() -> {            synchronized (obj) {                //001010 00 中的00示意轻量级锁其余62位指向领有锁的线程                //28 f6 5f 1d (00101000 11110110 01011111 00011101)                //00 00 00 00 (00000000 00000000 00000000 00000000)                System.out.println(Thread.currentThread().getName() + ":");                System.out.println(objClassLayout.toPrintable());            }        }, "t2");        thread2.start();        thread2.join();    }
偏差锁
    public void biasedLockTest() throws InterruptedException {        //提早让偏差锁启动        Thread.sleep(5000);        Object obj = new Object();        ClassLayout objClassLayout = ClassLayout.parseInstance(obj);        //1 01 匿名偏差锁 还未设置偏差线程        //05 00 00 00 (00000101 00000000 00000000 00000000)        //00 00 00 00 (00000000 00000000 00000000 00000000)        System.out.println(Thread.currentThread().getName() + ":");        System.out.println(objClassLayout.toPrintable());        synchronized (obj) {            //偏差锁 记录 线程地址            //05 30 e3 02 (00000101 00110000 11100011 00000010)            //00 00 00 00 (00000000 00000000 00000000 00000000)            System.out.println(Thread.currentThread().getName() + ":");            System.out.println(objClassLayout.toPrintable());        }        Thread thread1 = new Thread(() -> {            synchronized (obj) {                //收缩为轻量级 0 00 0未启用偏差锁,00轻量级锁                //68 f4 a8 1d (01101000 11110100 10101000 00011101)                //00 00 00 00 (00000000 00000000 00000000 00000000)                System.out.println(Thread.currentThread().getName() + ":");                System.out.println(objClassLayout.toPrintable());            }        }, "t1");        thread1.start();        thread1.join();    }
重量级锁
    public void heavyLockTest() throws InterruptedException {        Object obj = new Object();        ClassLayout objClassLayout = ClassLayout.parseInstance(obj);        Thread thread1 = new Thread(() -> {            synchronized (obj) {                //第一次 00 示意 轻量级锁                //d8 f1 c3 1e (11011000 11110001 11000011 00011110)                //00 00 00 00 (00000000 00000000 00000000 00000000)                System.out.println(Thread.currentThread().getName() + ":");                System.out.println(objClassLayout.toPrintable());                //用debug管制t2来竞争                //第二次打印 变成 10 示意收缩为重量级锁(t2竞争)  其余62位指向监视器对象                //fa 21 3e 1a (11111010 00100001 00111110 00011010)                //00 00 00 00 (00000000 00000000 00000000 00000000)                System.out.println(Thread.currentThread().getName() + ":");                System.out.println(objClassLayout.toPrintable());            }        }, "t1");        thread1.start();        Thread thread2 = new Thread(() -> {            synchronized (obj) {                //t2竞争 收缩为 重量级锁 111110 10 10为重量级锁                //fa 21 3e 1a (11111010 00100001 00111110 00011010)                //00 00 00 00 (00000000 00000000 00000000 00000000)                System.out.println(Thread.currentThread().getName() + ":");                System.out.println(objClassLayout.toPrintable());            }        }, "t2");        thread2.start();        thread1.join();        thread2.join();        //10 重量级锁 未产生锁降级        //3a 36 4d 1a (00111010 00110110 01001101 00011010)        //00 00 00 00 (00000000 00000000 00000000 00000000)        System.out.println(Thread.currentThread().getName() + ":");        System.out.println(objClassLayout.toPrintable());    }

轻量级锁

轻量级锁的提出是为了减小传统重量级锁应用互斥量(挂起/复原线程)所产生的开销

面对较少的竞争场景时,获取锁的工夫总是短暂的,而挂起线程用户态、内核态的开销比拟大,应用轻量级锁缩小开销

那么轻量级锁是如何实现的呢?

轻量级锁次要由lock record、mark word、CAS来实现,lock record存储在线程的栈帧中,来记录锁的信息

加锁

查看对象是不是无锁状态,如果对象是无锁状态,会将mark word复制到lock record锁记录中的displaced mark word

而后再尝试应用CAS尝试将mark word中局部内容替换指向这个lock record,如果胜利示意获取锁胜利

如果对象持有锁,会查看持有锁的线程是不是以后线程,这种可重入的状况下lock record中记录不再是mark word而是null

可重入的状况下,只须要进行自增计数即可,解锁时遇到null的lock record则扣减

如果CAS失败或者持有锁的线程不是以后线程,就会触发锁收缩

要害代码如下:

void ObjectSynchronizer::slow_enter(Handle obj, BasicLock* lock, TRAPS) {  //以后对象的mark word  markOop mark = obj->mark();  assert(!mark->has_bias_pattern(), "should not see bias pattern here");  //如果以后对象是无锁状态   if (mark->is_neutral()) {    //将mark word复制到lock record    lock->set_displaced_header(mark);    //CAS将以后对象的mark word内容替换为指向lock record    if (mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)) {      TEVENT (slow_enter: release stacklock) ;      return ;    }  } else  //如果有锁  判断是不是以后线程获取锁  if (mark->has_locker() && THREAD->is_lock_owned((address)mark->locker())) {    assert(lock != mark->locker(), "must not re-lock the same lock");    assert(lock != (BasicLock*)obj->mark(), "don't relock with same BasicLock");    //可重入锁 复制null    lock->set_displaced_header(NULL);    return;  }  //有锁并且获取锁的线程不是以后线程 或者 CAS失败 进行收缩  lock->set_displaced_header(markOopDesc::unused_mark());  ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD);}
解锁

查看lock record中复制的内容是不是空,是空阐明是可重入锁

不为空则查看mark word是否指向lock record,如果指向则CAS尝试将mark word记录指向lock record替换为lock record中的displaced mark word(也就是原来的mark word)

如果mark word不指向lock record 或者 CAS失败了 阐明存在竞争,其余线程加锁失败让mark word指向重量级锁,间接收缩

要害代码如下:

void ObjectSynchronizer::fast_exit(oop object, BasicLock* lock, TRAPS) {  assert(!object->mark()->has_bias_pattern(), "should not see bias pattern here");  //获取复制的mark word  markOop dhw = lock->displaced_header();  markOop mark ;  //如果为空 阐明是可重入  if (dhw == NULL) {     // Recursive stack-lock.     // Diagnostics -- Could be: stack-locked, inflating, inflated.     mark = object->mark() ;     assert (!mark->is_neutral(), "invariant") ;     if (mark->has_locker() && mark != markOopDesc::INFLATING()) {        assert(THREAD->is_lock_owned((address)mark->locker()), "invariant") ;     }     if (mark->has_monitor()) {        ObjectMonitor * m = mark->monitor() ;        assert(((oop)(m->object()))->mark() == mark, "invariant") ;        assert(m->is_entered(THREAD), "invariant") ;     }     return ;  }  mark = object->mark() ;  //如果mark word指向lock record  if (mark == (markOop) lock) {     assert (dhw->is_neutral(), "invariant") ;     //尝试CAS将指向lock record的mark word替换为原来的内容     if ((markOop) Atomic::cmpxchg_ptr (dhw, object->mark_addr(), mark) == mark) {        TEVENT (fast_exit: release stacklock) ;        return;     }  }  //未指向以后lock record或者CAS失败则收缩  ObjectSynchronizer::inflate(THREAD, object)->exit (true, THREAD) ;}

偏差锁

hotspot开发人员测试,在某些场景下,总是同一个线程获取锁,在这种场景下,心愿用更小的开销来获取锁

当开启偏差锁后,如果是无锁状态会将mark word改为偏差某个线程ID,以此标识这个线程获取锁(锁偏差这个线程)

如果正处于偏差锁,遇到竞争可能收缩为轻量级锁,如果要存储一致性哈希等状况也会收缩为重量级锁

JDK8默认开启偏差锁,在高版本JDK默认不开启偏差锁,可能因为偏差锁的保护超过收益,咱们也不深刻进行钻研

重量级锁

object monitor

应用object monitor对象来实现重量级锁

object monitor中应用一些字段记录信息,比方:object字段用于记录锁的那个对象,header字段用于记录锁的那个对象的mark word、owner字段用于记录持有锁的线程

object monitor应用阻塞队列来存储竞争不到锁的线程,应用期待队列来存储调用wait进入期待状态的线程

阻塞队列和期待队列类比着并发包下的AQS和Condition

object monitor应用cxq栈和entry list队列来实现阻塞队列,其中cxq栈中存储有竞争的线程,entry list存储曾经竞争失败较稳固的线程;应用wait set实现期待队列

当线程调用wait时,进入wait set期待队列

而调用notify时,只是将期待队列的队头节点退出cxq,并没有唤醒该线程去竞争

真正的唤醒线程是在开释锁时,去稳固的队列entry list中唤醒队头节点去竞争,而此时被唤醒的节点并不一定能抢到锁,因为线程进入cxq时还会通过自旋来抢锁,以此来实现非偏心锁

如果稳固的entry list中没有存储线程,会将cxq栈中存储的线程全存储到entry list中再去唤醒,此时越晚进入cxq的线程反而会越早被唤醒(cxq栈先进后出)

其实实现与AQS相似,来看这样一段代码:

t1-t6获取同一把锁,应用t1线程进行阻塞一会,后续t2-t6线程依照程序启动,因为自转获取不到锁,它们会被顺次放入cxq:t2,t3,t4,t5,t6

在t1开释锁时,因为entry list中没有线程,于是将cxq中的线程存入entry list:t6,t5,t4,t3,t2,再唤醒t6

因为后续没有线程进行竞争,因而最终执行程序为t1,t6,t5,t4,t3,t2

Object obj = new Object();new Thread(() -> {    synchronized (obj) {        try {            //输出阻塞            //阻塞的目标是让  其余线程自旋完未获取到锁,进入cxq栈            System.in.read();        } catch (IOException e) {            e.printStackTrace();        }        System.out.println(Thread.currentThread().getName() + " 获取到锁");    }}, "t1").start();//sleep控制线程阻塞的程序Thread.sleep(50);new Thread(() -> {    synchronized (obj) {        System.out.println(Thread.currentThread().getName() + " 获取到锁");    }}, "t2").start();Thread.sleep(50);new Thread(() -> {    synchronized (obj) {        System.out.println(Thread.currentThread().getName() + " 获取到锁");    }}, "t3").start();Thread.sleep(50);new Thread(() -> {    synchronized (obj) {        System.out.println(Thread.currentThread().getName() + " 获取到锁");    }}, "t4").start();Thread.sleep(50);new Thread(() -> {    synchronized (obj) {        System.out.println(Thread.currentThread().getName() + " 获取到锁");    }}, "t5").start();Thread.sleep(50);new Thread(() -> {    synchronized (obj) {        System.out.println(Thread.currentThread().getName() + " 获取到锁");    }}, "t6").start();

大抵理解了下object monitor,咱们再来看看收缩和自旋

收缩

在收缩时会有四种状态,别离是

inflated 已收缩:mark word锁标记为10(2)阐明已收缩,间接返回object monitor

inflation in progress 收缩中:如果曾经有其余线程在收缩了,就期待一会循环后查看状态进入已收缩的逻辑

stack-locked 轻量级锁收缩

neutral 无锁收缩

轻量级锁和无锁收缩逻辑差不多,都是须要创立object monitor对象,并且set一些属性进去(比方:mark word、锁的哪个对象、哪个线程持有锁...),最初再应用CAS去替换mark word指向object monitor

ObjectMonitor * ATTR ObjectSynchronizer::inflate (Thread * Self, oop object) {  for (;;) {      const markOop mark = object->mark() ;      assert (!mark->has_bias_pattern(), "invariant") ;      // The mark can be in one of the following states:      // *  Inflated     - just return      // *  Stack-locked - coerce it to inflated      // *  INFLATING    - busy wait for conversion to complete      // *  Neutral      - aggressively inflate the object.      // *  BIASED       - Illegal.  We should never see this      // CASE: inflated       // 已收缩:查看 mark word 后两位是否为2  是则收缩完 返回monitor对象      if (mark->has_monitor()) {          ObjectMonitor * inf = mark->monitor() ;          assert (inf->header()->is_neutral(), "invariant");          assert (inf->object() == object, "invariant") ;          assert (ObjectSynchronizer::verify_objmon_isinpool(inf), "monitor is invalid");          return inf ;      }      // CASE: inflation in progress - inflating over a stack-lock.      // 收缩中: 期待一会 再循环 从收缩完状态退出      if (mark == markOopDesc::INFLATING()) {         TEVENT (Inflate: spin while INFLATING) ;         ReadStableMark(object) ;         continue ;      }      // CASE: stack-locked      //轻量级锁收缩      if (mark->has_locker()) {          //创立ObjectMonitor          ObjectMonitor * m = omAlloc (Self) ;          m->Recycle();          m->_Responsible  = NULL ;          m->OwnerIsThread = 0 ;          m->_recursions   = 0 ;          m->_SpinDuration = ObjectMonitor::Knob_SpinLimit ;   // Consider: maintain by type/class          //cas将mark word替换指向ObjectMonitor          markOop cmp = (markOop) Atomic::cmpxchg_ptr (markOopDesc::INFLATING(), object->mark_addr(), mark) ;                   //cas 失败 则阐明其余线程收缩胜利,删除以后monitor 退出          if (cmp != mark) {             omRelease (Self, m, true) ;             continue ;       // Interference -- just retry          }          markOop dmw = mark->displaced_mark_helper() ;          assert (dmw->is_neutral(), "invariant") ;          //胜利 设置mark word          m->set_header(dmw) ;          //设置持有锁的线程          m->set_owner(mark->locker());          //设置锁的是哪个对象          m->set_object(object);          guarantee (object->mark() == markOopDesc::INFLATING(), "invariant") ;          //批改mark word对象头信息 锁状态 2          object->release_set_mark(markOopDesc::encode(m));          if (ObjectMonitor::_sync_Inflations != NULL) ObjectMonitor::_sync_Inflations->inc() ;          TEVENT(Inflate: overwrite stacklock) ;          if (TraceMonitorInflation) {            if (object->is_instance()) {              ResourceMark rm;              tty->print_cr("Inflating object " INTPTR_FORMAT " , mark " INTPTR_FORMAT " , type %s",                (void *) object, (intptr_t) object->mark(),                object->klass()->external_name());            }          }          return m ;      }      // CASE: neutral      //无锁收缩 与轻量级锁收缩相似,也是创立monitor对象并注入属性,只是很多属性为空      assert (mark->is_neutral(), "invariant");      ObjectMonitor * m = omAlloc (Self) ;      m->Recycle();      m->set_header(mark);      m->set_owner(NULL);      m->set_object(object);      m->OwnerIsThread = 1 ;      m->_recursions   = 0 ;      m->_Responsible  = NULL ;      m->_SpinDuration = ObjectMonitor::Knob_SpinLimit ;       // consider: keep metastats by type/class      //cas 更新 mark word 失败循环期待  胜利返回      if (Atomic::cmpxchg_ptr (markOopDesc::encode(m), object->mark_addr(), mark) != mark) {          m->set_object (NULL) ;          m->set_owner  (NULL) ;          m->OwnerIsThread = 0 ;          m->Recycle() ;          omRelease (Self, m, true) ;          m = NULL ;          continue ;      }           if (ObjectMonitor::_sync_Inflations != NULL) ObjectMonitor::_sync_Inflations->inc() ;      TEVENT(Inflate: overwrite neutral) ;      if (TraceMonitorInflation) {        if (object->is_instance()) {          ResourceMark rm;          tty->print_cr("Inflating object " INTPTR_FORMAT " , mark " INTPTR_FORMAT " , type %s",            (void *) object, (intptr_t) object->mark(),            object->klass()->external_name());        }      }      return m ;  }}
自旋

收缩过后,在最终挂起前会进行固定自旋和自适应自旋

固定自旋默认10+1次

自适应自旋一开始5000次,如果最近竞争少获取到锁就将自旋次数调大,如果最近竞争大获取不到锁就将自旋次数调小

int ObjectMonitor::TrySpin_VaryDuration (Thread * Self) {    // Dumb, brutal spin.  Good for comparative measurements against adaptive spinning.    int ctr = Knob_FixedSpin ;    if (ctr != 0) {        while (--ctr >= 0) {            if (TryLock (Self) > 0) return 1 ;            SpinPause () ;        }        return 0 ;    }    //先进行固定11自旋次数 获取到锁返回,没获取到空转    for (ctr = Knob_PreSpin + 1; --ctr >= 0 ; ) {      if (TryLock(Self) > 0) {        // Increase _SpinDuration ...        // Note that we don't clamp SpinDuration precisely at SpinLimit.        // Raising _SpurDuration to the poverty line is key.        int x = _SpinDuration ;        if (x < Knob_SpinLimit) {           if (x < Knob_Poverty) x = Knob_Poverty ;           _SpinDuration = x + Knob_BonusB ;        }        return 1 ;      }      SpinPause () ;    }        //自适应自旋 一开始5000 如果胜利认为此时竞争不大 自旋获取锁成功率高 减少重试次数 如果失败则缩小    //...}   

总结

本篇文章围绕synchronized,深入浅出的形容CAS、synchronized在Java层面和C++层面的实现、锁降级原理、案例、源码等

synchronized用于并发下的须要同步的场景,应用它能够满足原子性、可见性以及有序性,它能够作用在一般对象和动态对象,作用于动态对象时是去获取其对应的Class对象的锁

synchronized作用在代码块上时,应用monitorentry、monitorexit字节码指令来标识加锁、解锁;作用在办法上时,在拜访标识加锁synchronized关键字,虚拟机隐式应用monitorentry、monitorexit

CAS 比拟并替换,常与重试机制实现乐观锁/自旋锁,长处是可能在竞争小的场景用较小的开销取代线程挂起,但带来ABA问题、无奈预估重试次数空转CPU的开销等问题

轻量级锁的提出是为了在交替执行/竞争少的场景,用更小的开销取代互斥量;应用CAS和lock record实现

轻量级锁加锁时,如果是无锁则复制mark word到lock record中,再CAS将对象mark word替换为指向该lock record,失败则收缩;如果曾经持有锁则判断持有锁的线程是不是以后线程,是则累加次数,不是以后线程则收缩

轻量级锁解锁时,查看lock record复制的是不是null,是则阐明是可重入锁,次数减一;不是则CAS把复制过去的mark word替换回去,如果替换失败阐明其余线程竞争,mark word曾经指向object monitor,去指向重量级锁的开释

偏差锁的提出是为了在常常一条线程执行的场景下,用更小的开销来取代CAS的开销,只不过高版本不再默认开启

重量级锁由object monitor来实现,object monitor中应用cxq、entry list来形成阻塞队列,wait set来形成期待队列

当执行wait办法时,线程构建为节点退出wait set;当执行notify办法时,将wait set队头节点退出cxq,在开释锁时才去唤醒entry list队头节点竞争锁,即便没抢到锁构建为节点退出cxq时还会自旋,因而并不是entry list队头节点就肯定能抢到锁,以此来实现非偏心锁;当entry list为空时,将cxq栈中的节点退出entry list队列(后进入cxq的节点会被先唤醒)

在收缩为重量级锁时有四种状况,如果状态为已收缩则间接返回object monitor对象;如果状态为收缩中,阐明其余线程正在收缩,期待会,下次循环进入已收缩的逻辑;如果状态为轻量级锁收缩或无锁收缩,都会去创立object monitor对象,set一些重要属性,并CAS去将mark word替换为指向该object monitor

重量级锁在最终挂起前会进行固定自旋和自适应自旋(最近竞争小就减少自旋次数;竞争多就缩小自旋次数)

最初(不要白嫖,一键三连求求拉~)

本篇文章被支出专栏 由点到线,由线到面,深入浅出构建Java并发编程常识体系,感兴趣的同学能够继续关注喔

本篇文章笔记以及案例被支出 gitee-StudyJava、 github-StudyJava 感兴趣的同学能够stat下继续关注喔~

案例地址:

Gitee-JavaConcurrentProgramming/src/main/java/B_synchronized

Github-JavaConcurrentProgramming/src/main/java/B_synchronized

有什么问题能够在评论区交换,如果感觉菜菜写的不错,能够点赞、关注、珍藏反对一下~

关注菜菜,分享更多干货,公众号:菜菜的后端私房菜