synchronized使用及底层原理详解

多线程编程中,有可能会出现多个线程同时访问同一个共享、可变资源的情况,这个资源我们称之其为临界资源;这种资源可能是:对象、变量、文件等。

  • 共享:资源可以由多个线程同时访问
  • 可变:资源可以在其生命周期内被修改

引出的问题:

由于线程执行的过程是不可控的,所以需要采用同步机制来协同对对象可变状态的访问

加锁作用与锁定义分类

加锁目的:序列化访问临界资源,即同一时刻只能有一个线程访问临界资源(同步互斥访问)
在这里插入图片描述

在这里插入图片描述

synchronized原理详解

synchronized内置锁是一种对象锁(锁的是对象而非引用),作用粒度是对象,可以用来实现对临界资源的同步互斥访问,是可重入的。

  • 同步实例方法,锁是当前实例对象
  • 同步类方法,锁是当前类对象,静态方法
  • 同步代码块,锁是括号里面的对象

synchronized底层原理

synchronized是基于JVM内置锁实现,通过内部对象Monitor(监视器锁)实现,基于进入与退出Monitor对象实现方法与代码块同步,监视器锁的实现依赖底层操作系统的Mutex lock(互斥锁)实现,它是一个重量级锁性能较低。当然,JVM内置锁在1.5之后版本做了重大的优化,如锁粗化(LockCoarsening)、锁消除(Lock Elimination)、轻量级锁(Lightweight
Locking)、偏向锁(Biased Locking)、适应性自旋(Adaptive Spinning)等技术来减少锁操作的开销,,内置锁的并发性能已经基本与Lock持平。synchronized关键字被编译成字节码后会被翻译成monitorenter 和monitorexit 两条指令分别在同步块逻辑代码的起始位置与结束位置。
在这里插入图片描述
每个同步对象都有一个自己的Monitor(监视器锁),加锁过程如下图所示

在这里插入图片描述
是锁状态是被记录在每个对象的对象头(Mark Word)中,下面我们一起认识一下对象的内存布局

  • 对象头:比如 hash码,对象所属的年代,对象锁,锁状态标志,偏向 锁(线程)ID,偏向时间,数组长度(数组对象)等
  • 实例数据:即创建对象时,对象中成员变量,方法等
  • 对齐填充:对象的大小必须是8字节的整数倍
    在这里插入图片描述
    锁的膨胀升级过程

JDK1.6版本之后对synchronized的实现进行了各种优化,如自旋锁、偏向锁和轻量级锁

并默认开启偏向锁

开启偏向锁:-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0

关闭偏向锁:-XX:-UseBiasedLocking

在这里插入图片描述

锁的状态总共有四种,无锁状态、偏向锁、轻量级锁和重量级锁。随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级的重量级锁,但是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级。下图为锁的升级全过程:
在这里插入图片描述

注意:synchronized可重入的,随着锁升级,优化性比较高,相比于juc的lock,synchronized的优化空间更大

对象一定分配在堆中吗?
不一定,如果没有发生对象逃逸,对象会在栈上分配

锁的粗化

以下4步都有锁,优化后就会加一个总锁

public void test1(){
    
    
    //jvm的优化,锁的粗化
    stb.append("1");
    stb.append("2");
    stb.append("3");
    stb.append("4");
}

锁的消除

public void test2(){
    
    
    //jvm的优化,JVM不会对同步块进行加锁
    synchronized (new Object()) {
    
    
        //伪代码:很多逻辑
        //jvm是否会加锁?
        //jvm会进行逃逸分析
    }
}

猜你喜欢

转载自blog.csdn.net/qq_37904966/article/details/112643302