并发编程(二)Synchronized锁

基本使用

image-20201119190928500
  1. 修饰实例方法:锁的对象为当前实例 syn void
  2. 修饰静态方法:锁的对象为这个类 syn static void
  3. 修饰代码块:看锁对象还是实例 syn(a.class)、syn(this)

:不能用String常量、Integer、Long类型:因为Java自动封箱和解箱操作的原因

存储方式

对象头加锁前后变化

image-20201203192833465

参考工具:Java Object Layout

img
可以看出锁信息存储在对象的对象头当中:对应源码(markOop.hpp当中)

img

对象头(marword):8字节对象头+4字节的指向指针(属于哪个类)

对象填充:8的倍数,缺少则填充

不同类型的锁在对象头中存储的结构如下:

image-20201203193114155

锁升级的过程

Jdk1.6之前,基于重量级锁来实现

无锁->偏向锁->轻量级锁->重量级锁

偏向锁

image-20201119232307183

存在的意义:大多数情况下不存在锁竞争,都是由同一个线程多次获取

通过cas实现原子性

关闭方式:-usebiasedLock

:线程1执行完毕后,不会主动去释放偏向锁。线程2竞争时,先判断偏向锁标记为1,然后在判断持有的线程1是否还存活,不存活,cas替换,存活则暂停升级。

偏向锁延迟打开的作用:JVM启动过程中会去对对象处理,这个时候会存在锁竞争问题,因此让偏向锁延迟打开。-XX:BiasedLockingStartupDelay默认4秒

偏向锁若启用,则创建的对象为匿名偏向锁对象1|01,此时记录的线程id为空

轻量级锁

image-20201119232325852

偏向锁撤销后升级为轻量级锁,通过cas自旋获取锁

自旋的意义:绝大部分线程在获取线程后,在非常短的时间内会释放

设置自旋次数:preBlockSpin,默认10次

jdk1.6后引入自适应自旋:由JVM来控制(根据最近成功率调整自旋次数)

image-20201119232657942

轻量级锁加锁过程

  1. 线程栈桢创建LockRecord并复制MarkWord中的锁记录
  2. 将LockRecord中的Owner指针指向锁对象
  3. 将锁对象的MarkWord替换为指向LockRecord的指针

重量级锁(Mutex)

image-20201122174651665

基于对象监视器实现。

同步队列:objectMonotor.hpp中的waitSet队列

偏向锁直接升级成重量级锁:当获取偏向锁的对象调用了wait方法的时候会直接升级为重量级锁

加锁过程

  1. monitor依赖操作系统的MutexLock(互斥锁)来实现的,线程被阻塞后便进入内核(Linux)调度状态,这个会导致系统在用户态与内核态之间来回切换,严重影响锁的性能
  2. 任意线程对Object的访问,首先要获取对象的monitor,获取失败加入同步队列,线程状态变为BLOCK,访问Object的前驱释放锁,唤醒队列中的线程重新尝试获得锁

锁重入

偏向锁和轻量级锁->线程栈中lockRecord+1

重量级锁->ObjectMonitor当中

锁消除

方法内部的锁只会被自己线程所用(如StringBuffer类的append操作),不可能被其他线程引用,则自动消除对象内部锁。

锁粗化

JVM检测多次操作对同一个对象加锁(如while操作),则会将锁范围加粗到方法体外,是这一串操作只进行一次加锁。

wait、notify、notifyall

image-20201122175826293

线程通信机制

wait:实现线程阻塞,释放锁当前的syn锁

notify/notifyall:唤醒被阻塞的线程

notify唤醒线程后需要重新加入竞争锁队列,不会直接获得

wait和sleep的区别:

wait释放锁和cpu资源,sleep不会释放锁资源

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值