synchronized锁的底层原理

参考B站黑马视频学习:https://www.bilibili.com/video/BV1yT411H7YK

一、Synchronized关键字

Synchronized是Java中用于实现线程同步的关键字。它可以应用于方法或代码块,用于控制多个线程对共享资源的访问。通过使用Synchronized,我们可以确保在同一时间只有一个线程能够执行被Synchronized修饰的代码,从而保证了线程安全性。

Synchronized关键字的使用和工作方式:

示例1:Synchronized方法

public class Counter {
    private int count;

    public synchronized void increment() {
        count++;
    }
}

在上面的示例中,increment()方法被标记为synchronized,这意味着同一时间只有一个线程可以执行该方法。当一个线程进入increment()方法时,它会获取到Counter对象的监视器锁,并执行方法体中的代码。其他线程如果尝试调用increment()方法,将会被阻塞,直到锁被释放。

示例2:Synchronized代码块

public class Counter {
    private int count;
    private Object lock = new Object();

    public void increment() {
        synchronized (lock) {
            count++;
        }
    }
}

在上面的示例中,我们使用了一个对象lock作为锁对象,并在synchronized代码块中使用它。这样做的好处是,我们可以控制锁的粒度,只对需要同步的代码块进行加锁,而不是整个方法。当一个线程进入synchronized代码块时,它会获取到lock对象的监视器锁,并执行代码块中的代码。其他线程如果尝试获取lock对象的锁,将会被阻塞,直到锁被释放。

示例3:静态方法的同步

public class Counter {
    private static int count;

    public static synchronized void increment() {
        count++;
    }
}

在上面的示例中,我们将synchronized关键字应用于静态方法。这意味着同一时间只有一个线程可以执行该静态方法。当一个线程进入increment()方法时,它会获取到Counter类的Class对象的监视器锁,并执行方法体中的代码。其他线程如果尝试调用increment()方法,将会被阻塞,直到锁被释放。

synchronized关键字提供了简单且有效的线程同步机制,但需要注意以下几点:

  • synchronized关键字只能同步代码块或方法,不能用于变量或类。
  • synchronized关键字是可重入的,一个线程可以多次获取同一个锁。
  • synchronized关键字是独占锁,同一时间只能有一个线程获取锁。
  • synchronized关键字保证了线程的可见性和有序性,一个线程对共享变量的修改对其他线程是可见的。

二、Synchronized关键字的底层原理

synchronized 底层使用的JVM级别中的Monitor 来决定当前线程是否获得了锁,如果某一个线程获得了锁,在没有释放锁之前,其他线程是不能或得到锁的。synchronized 属于悲观锁。

synchronized 因为需要依赖于JVM级别的Monitor ,相对性能也比较低。

Monitor对象

monitor对象存在于每个Java对象的对象头中,synchronized 锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因
在这里插入图片描述

monitor内部维护了三个变量:

  • WaitSet:保存处于Waiting状态的线程
  • EntryList:保存处于Blocked状态的线程
  • Owner:持有锁的线程

具体的流程:

  • 代码进入synchorized代码块,先让lock(对象锁)关联的monitor,然后判断Owner是否有线程持有
  • 如果没有线程持有,则让当前线程持有,表示该线程获取锁成功
  • 如果有线程持有,则让当前线程进入entryList进行阻塞,如果Owner持有的线程已经释放了锁,在EntryList中的线程去竞争锁的持有权(非公平)
  • 如果代码块中调用了wait()方法,则会进去WaitSet中进行等待

只有一个线程获取到的标志就是在monitor中设置成功了Owner,一个monitor中只能有一个Owner

在上锁的过程中,如果有其他线程也来抢锁,则进入EntryList 进行阻塞,当获得锁的线程执行完了,释放了锁,就会唤醒EntryList 中等待的线程竞争锁,竞争的时候是非公平的。

synchronized的锁升级

在Java中,锁的升级过程是为了在不同的竞争情况下提供最佳的性能和资源利用。锁的升级过程可以分为以下几个阶段:

  1. 无锁状态:在对象被创建时,默认处于无锁状态。此时,任何线程都可以自由地访问和修改对象的数据,没有任何同步措施。
  2. 偏向锁(Biased Locking):当一个线程第一次访问一个对象时,会尝试获取偏向锁。在第一次获得锁时,会有一个CAS操作,之后该线程再获取锁,只需要判断mark word中是否是自己的线程id即可,而不是开销相对较大的CAS命令
  3. 轻量级锁(Lightweight Locking):如果另一个线程尝试获取已经被偏向的锁,偏向锁就会升级为轻量级锁。轻量级修改了对象头的锁标志,相对重量级锁性能提升很多。每次修改都是CAS操作,保证原子性。如果CAS成功,线程可以继续执行,否则就会进一步升级为重量级锁。
  4. 重量级锁(Heavyweight Locking):如果轻量级锁的CAS操作失败,表示存在竞争情况,锁就会升级为重量级锁。重量级锁底层使用的Monitor实现,里面涉及到了用户态和内核态的切换、进程的上下文切换,成本较高,性能比较低。

一旦锁发生了竞争,都会升级为重量级锁

需要注意的是,锁的升级是逐级升级的过程,而不是直接跳跃到最高级别的锁。这是为了避免不必要的开销和资源浪费。锁的升级过程是由JVM自动管理的,根据线程的竞争情况和访问模式来判断是否需要升级锁的状态。

锁的升级过程是为了提供最佳的性能和资源利用。在无竞争情况下,偏向锁可以减少锁操作的开销。在轻量级竞争情况下,轻量级锁可以提供更好的性能。而在重度竞争情况下,重量级锁可以保证线程的同步和数据的一致性。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

abcccccccccccccccode

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

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

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

打赏作者

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

抵扣说明:

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

余额充值