15000字、6个代码案例、5个原理图让你彻底搞懂Synchronized

news2024/11/17 19:33:53

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) {

        }

image.png

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

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可能和我注释中的不同,我们主要查看锁标识的值和是否启用偏向锁

image.png

    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

image.png

然后再尝试使用CAS尝试将mark word中部分内容替换指向这个lock record,如果成功表示获取锁成功

image.png

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

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

image.png

如果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指向重量级锁,直接膨胀

image.png

关键代码如下:

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

有什么问题可以在评论区交流,如果觉得菜菜写的不错,可以点赞、关注、收藏支持一下~

.cn/column/7270046881542766604),感兴趣的同学可以持续关注喔

本篇文章笔记以及案例被收入 gitee-StudyJava、 github-StudyJava 感兴趣的同学可以stat下持续关注喔~

案例地址:

Gitee-JavaConcurrentProgramming/src/main/java/B_synchronized

Github-JavaConcurrentProgramming/src/main/java/B_synchronized

有什么问题可以在评论区交流,如果觉得菜菜写的不错,可以点赞、关注、收藏支持一下~

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/962924.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

03_nodjs_npm的使用

03 【npm的使用】 1.包和npm 1.1 什么是包 由于 Node 是一套轻内核的平台&#xff0c;虽然提供了一系列的内置模块&#xff0c;但是不足以满足开发者的需求&#xff0c;于是乎出现了包&#xff08;package&#xff09;的概念&#xff1a; 与核心模块类似&#xff0c;就是将一…

[学习笔记]斜率优化dp 总结

前言&#xff1a; 我们学过不少优化类的算法了&#xff0c;大部分都是基于凸函数的性质给出的优化&#xff0c;比如Slope Trick&#xff0c;Wqs二分&#xff0c;又比如今天的斜率优化&#xff08;不知道什么时候会有空把Slope Trick写掉&#xff09; 正文&#xff1a; 我们考…

这个在线网站让你三分钟制作出一份精美简历

今天&#xff0c;我要向大家推荐一个神奇的在线工具网站&#xff0c;它能够提供免费简历模板、简历范文&#xff0c;支持在线编辑&#xff0c;并且一键下载为PDF。这个工具让你的简历制作变得轻松便捷&#xff01; 首先&#xff0c;这个网站的简历模板非常丰富多样。无论你是刚…

bazel构建原理

调度模型 传统构建系统有很多是基于任务的&#xff0c;例如 Ant&#xff0c;Maven&#xff0c;Gradle。用户可以自定义"任务"(Task&#xff09;&#xff0c;例如执行一段 shell 脚本。用户配置它们的依赖关系&#xff0c;构建系统则按照顺序调度。 基于 Task 的调度…

揭秘亚马逊Amazon测评,掌握细节和技巧,提升产品销量和评论数量

亚马逊是全球最大的跨境电商平台&#xff0c;拥有全球65个国家的几十个网站。对于跨境卖家来说&#xff0c;亚马逊是最值得选择的平台之一。 亚马逊的八大站点分别是美国、加拿大、墨西哥、欧洲、澳大利亚、日本、中东和巴西。 美国站点是全球最大的零售市场&#xff0c;拥有…

区块链实验室(18) - 用FISCO BCOS架设1个无标度网络

FISCO技术文档提供1个4节点的网络案例&#xff0c;这4个节点构成1个强连通图。强连通图在现实中通常是不存在的。 本文用FISCO架设1个网络&#xff0c;该网络由100个节点构成1个无标度(scale free)网络&#xff0c;如下图所示。 1 用FISCO工具构建1个100节点的初始网络 FISCO提…

Jenkins java8安装版本安装

一、首先准备Jenkins、Jdk8、Tomcat9安装包 根据Jenkins官网介绍&#xff0c;Jenkins支持Java8的版本如下&#xff1a; 我们选择2.164版本进行安装&#xff0c;根据版本号支持输入下载地址&#xff1a;https://archives.jenkins.io/war/2.164/jenkins.war&#xff0c;进行下载…

【牛客网题目】合并两个排序的链表

目录 描述 题目分析 描述 输入两个递增的链表&#xff0c;单个链表的长度为n&#xff0c;合并这两个链表并使新链表中的节点仍然是递增排序的。 数据范围&#xff1a;0≤n≤1000&#xff0c;1000≤节点值≤1000 要求&#xff1a;空间复杂度 O(1)&#xff0c;时间复杂…

为什么我说 90% 的技术人搞副业都会失败?!

见字如面&#xff0c;我是军哥&#xff01; 这几天有多位程序员读者请教我&#xff0c;如何搞副业&#xff1f;并且有一位已经搞了半年了&#xff0c;涨粉困难&#xff0c;这小伙子大概工作 2 年的样子&#xff0c;所在的公司算中等二线大厂吧。 我把和他们聊的内容总结了一下&…

vscode调教配置:快捷修复和格式化代码

配置vscode快捷键&#xff0c;让你像使用idea一样使用vscode&#xff0c;我们最常用的两个功能就是格式化代码和快捷修复&#xff0c;所以这里修改一下快捷修复和格式化代码的快捷键。 在设置中&#xff0c;找到快捷键配置&#xff1a; 然后搜索&#xff1a;快捷修复 在快捷键…

分库分表篇-1 分库分表介绍

文章目录 前言一、为什么要分库分表&#xff1f;二、分库分表的策略&#xff1a;2.1 垂直切分&#xff1a;2.2.1 垂直分库&#xff1a;2.2.2 垂直分表&#xff1a;2.2.3 垂直拆分的优缺点: 2.2 水平切分&#xff1a;2.2.1 库内分表:2.2.2 分库分表:2.2.3 水平切分优缺点: 三、分…

04-MySQL02

1、什么是索引下推&#xff1f; 索引下推&#xff08;index condition pushdown &#xff09;简称ICP&#xff0c;在Mysql5.6的版本上推出&#xff0c;用于优化查询。 需求: 查询users表中 "名字第一个字是张&#xff0c;年龄为10岁的所有记录"。 SELECT * FROM u…

无涯教程-JavaScript - FTEST函数

FTEST函数取代了Excel 2010中的F.TEST函数。 描述 该函数返回F检验的输出。 F检验返回两尾概率,即array1和array2的方差没有显着差异。使用此功能可以确定两个样本是否具有不同的方差。 语法 FTEST (array1, array2)争论 Argument描述Required/OptionalArray1The first ar…

【仿写spring之ioc篇】三、检查是否实现了Aware接口并且执行对应的方法

Aware接口 Aware接口中只是设置了对应的set方法&#xff0c;目前只定义了三个Aware 以BeanNameAware为例 package com.ez4sterben.spring.ioc.factory.aware;/*** bean名字清楚** author ez4sterben* date 2023/08/31*/ public interface BeanNameAware {/*** 设置beanName* …

老胡的周刊(第106期)

老胡的信息周刊[1]&#xff0c;记录这周我看到的有价值的信息&#xff0c;主要针对计算机领域&#xff0c;内容主题极大程度被我个人喜好主导。这个项目核心目的在于记录让自己有印象的信息做一个留存以及共享。 &#x1f3af; 项目 quivr[2] Quivr 是您在云中的第二个大脑&…

Mysql数据库事务隔离级别造成死锁

场景:如下代码,获取数据库连接,删除权限的时候,会造成数据库死锁. 代码 日志&#xff1a; 数据库&#xff1a; SHOW OPEN TABLES where In_use > 0; 问题分析&#xff1a;测试环境Centos7操作系统&#xff0c;Mysql5.7.40版本程序运行正常&#xff0c;开发环境Windows操…

设计模式行为型-模板模式

文章目录 一&#xff1a;模板方法设计模式概述1.1 简介1.2 定义和目的1.3 关键特点1.4 适用场景 二&#xff1a;模板方法设计模式基本原理2.1 抽象类2.1.1 定义和作用2.1.2 模板方法2.1.3 具体方法 2.2 具体类2.2.1 定义和作用2.2.2 实现抽象类中的抽象方法2.2.3 覆盖钩子方法 …

学生信息管理系统MIS(前端)

改造HTML文件 <!DOCTYPE html> <html><head><meta charset"utf-8"><title>学生信息管理系统MIS</title><!-- link在HTML文件中,引入外部的css文件 rel的值是固定写法,stylesheet样式表href用来指定样式表的位置--><lin…

CleanMyMac X2024软件最新版本更新下载

CleanMyMac X是一款颇受欢迎的专业清理软件&#xff0c;拥有十多项强大的功能&#xff0c;可以进行系统清理、清空废纸篓、清除大旧型文件、程序卸载、除恶意软件、系统维护等等&#xff0c;并且这款清理软件操作简易&#xff0c;非常好上手&#xff0c;特别适用于那些刚入手苹…

企业架构LNMP学习笔记5

Nginx&#xff1a; 常见用法&#xff1a; 1&#xff09;web服务器软件 httpd http协议 同类的web服务器软件&#xff1a;apache Nginx&#xff08;俄罗斯&#xff09;IIS&#xff08;微软&#xff09;lighttpd&#xff08;德国&#xff09; 2&#xff09;代理服务器 反向代…