并发编程夯实之路-synchronized

synchronized关键字

可用来给对象和方法或者代码块加锁,当它锁定一个方法或者一个代码块的时候,同一时刻最多只有一个线程执行这段代码。可能锁对象包括: this, 临界资源对象,Class 类对象

同步方法

同步方法锁定的是当前对象。当多线程通过同一个对象引用多次调用当前同步方法时, 需同步执行。

public synchronized void test() {
          System.out.println("测试一下");
    }

同步代码块

同步代码块的同步粒度更加细致,是商业开发中推荐的编程方式。可以定位到具体的同步位置,而不是简单的将方法整体实现同步逻辑。在效率上,相对更高。
锁定临界对象
同步代码块在执行时,是锁定 object 对象。当多个线程调用同一个方法时,锁定对象不变的情况下,需同步执行。

public void test() {
        synchronized(o) {
            System.out.println("测试一下");
        }
    }

锁定当前对象

public void test() {
        synchronized(this) {
            System.out.println("测试一下");
        }
    }

synchronized底层原理

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

每个同步对象都有一个自己的Monitor(监视器锁),加锁过程如下图所示:
在这里插入图片描述
 

对象的内存布局

HotSpot虚拟机中,对象在内存中存储的布局可以分为三块区域:对象头(Header)、实例数据(Instance Data)和对齐填充 (Padding)

  • 对象头:比如 hash码,对象所属的年代,对象锁,锁状态标志,偏向锁(线程)ID,偏向时间,数组长度(数组对象) 等。
  • 实例数据: 存放类的属性数据信息,包括父类的属性信息;
  • 对齐填充:由于虚拟机要求 对象起始地址必须是8字节的整数倍。填充数据不是必须存在的,仅仅是为了字节对齐;
     

在这里插入图片描述
 

对象头

HotSpot虚拟机的对象头包括两部分信息,第一部分是“Mark Word”,用于存储对象自身的运行时数据, 如哈希码 (HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等等,它是实现轻量级锁和偏向锁的关 键。,这部分数据的长度在32位和64位的虚拟机(暂 不考虑开启压缩指针的场景)中分别为32个和64个Bits,官方称它为“Mark Word”。

在这里插入图片描述
 

锁的膨胀升级过程

加锁可以使一段代码在同一时间只有一个线程可以访问,在增加安全性的同时,牺牲掉的是程序的执行性能,所以为了在一定程度上减少获得锁和释放锁带来的性能消耗,在 jdk6 之后便引入了“偏向锁”和“轻量级锁”,所以总共有4种锁状态,级别由低到高依次为:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态。这几个状态会随着竞争情况逐渐升级,此过程为不可逆。所以 synchronized 锁膨胀过程其实就是无锁 → 偏向锁 → 轻量级锁 → 重量级锁的一个过程。

在这里插入图片描述

  • 偏向锁: 刚执行到Synchronized关键字时的锁对象为偏向锁(偏向第一个申请到它的线程)(通过CAS操作修改对象头里的锁标志位),当该线程执行完之后,锁不会被释放;当第二次执行到同步代码块时,线程会判断当前持有锁的线程是否就是自己(对象头里有持有锁的线程ID),若是则继续往下执行,不需要重新加锁;若不是,则会把偏向锁升级为轻量级锁。
  • 轻量级锁: 当有第二个线程加入锁竞争时,偏向锁就会升级为轻量级锁。轻量级锁是自旋锁,即当一个线程申请锁而不得时,该线程就会进入自旋(为什么是自旋而不是挂起呢?因为挂起和恢复需要在用户态和内核态之间切换,会造成较大的开销,而短时间的自旋开销更小,不需要切换状态)。
  • 重量级锁: 若某线程忙等次数过多大于设置的阈值,说明锁竞争情况严重(长时间的自旋会造成CPU资源的浪费,开销变大),因此这个达到最大自旋次数的线程就会将轻量级锁升级为重量级锁(CAS操作修改锁标志位),将自己挂起,放弃CPU,等待未来被唤醒。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值