0
点赞
收藏
分享

微信扫一扫

synchronized 详解

彩虹_bd07 2022-04-14 阅读 105
java

语法

 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 之后不能控制是否开启自旋功能
举报

相关推荐

0 条评论