Synchronized 详解

语法

 synchronized(锁对象) // 线程1, 线程2(blocked)
{
 临界区
}

注意

  • 如果 t1 synchronized(obj1) 而 t2 synchronized(obj2) 会怎样?
    此种方式相当于多个线程持有不同的锁,不会起到保证临界区代码是原子性操作,达不到预期
  • 如果 t1 synchronized(obj) 而 t2 没有加会怎么样?
    此种方式相当于一个线程持有锁一个线程没有锁,不会起到保证临界区代码是原子性操作,达不到预期

方法上的synchronized形式

  • 形式一
    加在非静态方法上,锁对象为调用者本身
class Test{
 public synchronized void test() {
 	}
}
等价于
class Test{
 public void test() {
 synchronized(this) {
 		}
 	}
}
  • 形式一
    加在静态方法上,锁对象为类对象
class Test{
 public synchronized static void test() {
 	}
}
等价于
class Test{
 public static void test() {
 synchronized(Test.class) {
 		}
 	}
}

对象头

在这里插入图片描述

Monitor(锁)

每个 Java 对象都可以关联一个 Monitor 对象,如果使用 synchronized 给对象上锁(重量级)之后,该对象头的Mark Word 中就被设置指向 Monitor 对象的指针
在这里插入图片描述

执行流程

  • 刚开始 Monitor 中 Owner 为 null
  • 当 Thread-2 执行 synchronized(obj) 就会将 Monitor 的所有者 Owner 置为 Thread-2,Monitor中只能有一个 Owner
  • 在 Thread-2 上锁的过程中,如果 Thread-3,Thread-4 也来执行 synchronized(obj),就会进入EntryList BLOCKED
  • Thread-2 执行完同步代码块的内容,然后唤醒 EntryList 中等待的线程来竞争锁,竞争时是非公平的
  • 图中 WaitSet 中的 Thread-0,Thread-1 是之前获得过锁,条件不满足时调用wait() 进入 WAITING 状态的线程

注意

  • synchronized 对线程加锁时,该锁对象必须为重量级锁才会有上述效果
  • synchronized 必须是进入同一个对象的 monitor 才有上述的效果
  • 不加 synchronized 的对象不会关联监视器,不遵从以上规则

锁变化过程

在这里插入图片描述

锁消除

  • 这是一种JIT优化代码得方案,如果加的锁对象,而这个锁对象又是局部变量,又不会被外部锁引用,此时就会将锁进行消除。

偏向锁

相关概念
  • 只有第一次使用 CAS 将线程 ID 设置到对象头的 Mark Word ,之后发现这个线程 ID 是自己的就表示没有竞争,不用重新 CAS。以后只要不发生竞争,这个对象就归该线程所有
  • JDK6引入此概念,来优化没有线程竞争锁时,每次重入加轻量级锁 CAS 操作
  • 如果开启了偏向锁(默认开启),那么对象创建后,markword 值为 0x05 即最后 3 位为 101,这时它的thread、epoch、age 都为 0
  • 偏向锁是默认是延迟的,不会在程序启动时立即生效,如果想避免延迟,可以加 VM 参数 -
    XX:BiasedLockingStartupDelay=0 来禁用延迟
  • 如果没有开启偏向锁,那么对象创建后,markword 值为 0x01 即最后 3 位为 001,这时它的 hashcode、age 都为 0,第一次用到 hashcode 时才会赋值
  • 注意:处于偏向锁的对象解锁后,线程 id 仍存储于对象头中
撤销偏向锁

方式一:调用对象 hashCode

  • 原因:因为hashCode(31位)会保存在对象头中,如果在存在偏向锁,对象头中又要存储线程ID(54位),这时就会在存储线程id及hashcode之间冲突,所以调用对象的hashCode方法会撤销偏向锁
  • 轻量级锁会在锁记录中记录 hashCode
  • 重量级锁会在 Monitor 中记录 hashCode

方式二:其它线程使用对象

  • 原因:当有其它线程使用偏向锁对象时,会将偏向锁升级为轻量级锁

方式三:调用 wait/notify

  • 原因:因为这两个方法都会涉及monitor对象,而monitor又只会在重量级锁中出现,所以如果调用了这两个方法会导致偏向锁撤销
批量重偏向
  • 如果对象虽然被多个线程访问,但没有竞争,这时偏向了线程 T1 的对象仍有机会重新偏向 T2,重偏向会重置对象的 Thread ID
  • 当撤销偏向锁阈值超过 20 次后,jvm 会认为是不是偏向错了,于是会在给这些对象加锁时重新偏向至加锁线程
批量撤销
  • 当撤销偏向锁阈值超过 40 次后,jvm 会认为确实偏向错了,根本就不该偏向。于是整个类的所有对象都会变为不可偏向的,新建的对象也是不可偏向的

轻量级锁

  • 轻量级锁的使用场景:如果一个对象虽然有多线程要加锁,但加锁的时间是错开的(也就是没有竞争),那么可以使用轻量级锁来优化
  • 轻量级锁对使用者是透明的,即语法仍然是 synchronized
实例代码
static final Object obj = new Object();
public static void method1() {
 synchronized( obj ) {
 // 同步块 A
 method2();
 }
}
public static void method2() {
 synchronized( obj ) {
 // 同步块 B
 }
}
加解锁过程

在这里插入图片描述

重量级锁

  • 示意图与monitor结构示意图类似,这里不在赘述
重量级锁自旋优化
  • 重量级锁竞争的时候,还可以使用自旋来进行优化,如果当前线程自旋成功(即这时候持锁线程已经退出了同步块,释放了锁),这时当前线程就可以避免阻塞。
  • 自旋会占用 CPU 时间,单核 CPU 自旋就是浪费,多核 CPU 自旋才能发挥优势。
  • 在 Java 6 之后自旋锁是自适应的,比如对象刚刚的一次自旋操作成功过,那么认为这次自旋成功的可能性会高,就多自旋几次;反之,就少自旋甚至不自旋,总之,比较智能
  • Java 7 之后不能控制是否开启自旋功能

与ThreadLocal 的区别

  • synchronized 设计思路:采用 “以时间换空间” 的思路,共享变量只有一份,让不同的线程排队访问。
  • ThreadLocal 设计思路:采用 “以空间换时间的” 的思路,将共享变量进行副本复制,让不同的线程拥有自己共享变量副本,从而保证共享变量在不同的线程之间进行隔离,解决并发访问变量问题

ThreadLocal 详解:https://blog.csdn.net/silence_yb/article/details/124265702

  • 3
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

似寒若暖

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值