likes
comments
collection
share

查漏补缺第二期(synchronized & 锁升级)

作者站长头像
站长
· 阅读数 13

前言

目前正在出一个查漏补缺专题系列教程, 篇幅会较多, 喜欢的话,给个关注❤️ ~

本专题主要以Java语言为主, 好了, 废话不多说直接开整吧~

Q1 & 请说说对synchronized的理解

Java中,关键字synchronized用于实现多线程之间的同步。它可以应用于方法或代码块,并确保在同一时间只有一个线程可以访问被同步的代码段.

  • 保证原子性:synchronized关键字用于对临界区代码进行加锁,确保多个线程无法同时执行被同步的代码块或方法。这样可以保证代码块或方法中的操作是原子的, 即要么全部执行完毕,要么不执行,从而避免了竞态条件(race condition)的问题。

  • 内置锁(Intrinsic Lock):在Java中,每个对象都有一个内置的锁,也称为监视器锁或互斥锁。当一个线程进入synchronized代码块时,它必须首先获得对象的内置锁,其他线程必须等待直到锁被释放。这样确保了同一时间只有一个线程能够执行被同步的代码块。

  • 互斥性和可见性:synchronized关键字不仅提供了互斥访问,还确保了可见性。当一个线程获取到锁时,它会清空工作内存中的变量副本,强制从主内存中重新加载变量的值。这样可以确保线程在访问变量时获取最新的值,而不是使用过期的副本。

  • 锁的粒度:

    • 对象锁:当使用synchronized修饰实例方法时,锁定的是当前对象实例。这意味着同一时间只有一个线程可以访问该实例的synchronized方法,不同的实例之间互不影响。
    • 类锁:当使用synchronized修饰静态方法时,锁定的是整个类对象。这意味着同一时间只有一个线程可以访问该类的任意一个synchronized静态方法,无论是不同实例还是相同实例。
  • 重入性:Java中的synchronized关键字支持重入性。也就是说,如果一个线程已经获得了一个对象的锁,它可以再次获取该对象的锁而不会被阻塞。这种机制允许线程在同步代码块内部调用其他同步方法,避免了自己阻塞自己的情况。

总之,Java中的synchronized关键字提供了一种简单而有效的方法来确保多线程之间的同步和线程安全性。它通过使用内置锁、互斥性和可见性保证了临界区代码的原子性执行,避免了数据竞争和数据不一致的问题。

下面我们通过一些例子复习一下:

  • 同步代码块(Synchronized Blocks):除了使用synchronized修饰整个方法外,还可以使用synchronized修饰一段代码块,称为同步代码块。这样可以在方法中只对某一部分代码进行同步,而不是整个方法。
public void someMethod() {
    // 非同步代码
    synchronized (this) {
        // 需要同步的代码
    }
    // 非同步代码
}

在上面的示例中,使用synchronized(this)将某个对象作为锁来同步代码块,只有一个线程可以进入同步代码块,其他线程需要等待。

  • 同步方法(Synchronized Methods):这是synchronized的常见用法,将关键字直接应用于方法上,以实现整个方法的同步。当一个线程进入同步方法时,它会获得方法所属对象的锁,其他线程需要等待。
public synchronized void someMethod() {
    // 需要同步的代码
}

  • 同步静态方法(Synchronized Static Methods):类级别的方法可以使用synchronized关键字进行同步。与同步方法类似,但是锁的范围是该方法所在的类对象。
public static synchronized void someMethod() {
    // 需要同步的代码
}

  • 同步块的对象锁(Object Lock of Synchronized Blocks):在同步代码块中,可以使用任意的对象作为锁,而不仅仅是this关键字。这使得我们可以更细粒度地控制同步。
public void someMethod() {
    Object lock = new Object();
    // 非同步代码
    synchronized (lock) {
        // 需要同步的代码
    }
    // 非同步代码
}


  • 同步锁的重入(Reentrant Synchronization):在Java中,一个线程在获得一个对象锁后,可以再次获得该对象的锁。这种重入机制可以避免线程死锁
public synchronized void methodA() {
    // 执行一些操作
    methodB(); // 可重入,可以再次获得锁
    // 执行一些操作
}

public synchronized void methodB() {
    // 执行一些操作
}


Q2 & synchronized锁升级有了解过吗,它是怎么样的一个过程?

锁升级是指在Java中,synchronized关键字在不同的情况下可以升级使用不同级别的锁,以提高性能和减少开销。Java中的锁升级主要涉及到偏向锁、轻量级锁和重量级锁这三个级别。

  • 偏向锁(Biased Locking): 偏向锁是指当只有一个线程访问同步块时,为了减少同步开销,JVM会自动将对象的锁记录下来,标记为偏向锁状态。当其他线程访问该同步块时,不需要竞争锁,而是直接获取锁。

  • 锁升级过程:

    • 初始状态:对象没有锁标记。
    • 偏向锁申请:当第一个线程访问同步块时,JVM将该线程ID记录在对象头部,并将对象的标记状态设置为偏向锁。
    • 偏向锁撤销:当其他线程尝试获取锁时,发现对象的偏向锁被占用,会撤销偏向锁,升级为轻量级锁。
public class BiasedLockExample {
    private static final Object lock = new Object();

    public static void main(String[] args) {
        synchronized (lock) {
            // 同步块
        }
    }
}
  • 轻量级锁(Lightweight Locking)轻量级锁是指当多个线程轻度竞争同步块时,JVM会将对象的锁记录在线程的栈帧中,而不是在对象头中。线程在进入同步块之前,通过CAS(比较并交换)操作尝试获取锁。如果CAS成功,则表示获取锁成功,进入同步块;如果CAS失败,表示存在竞争,升级为重量级锁。

  • 锁升级过程:

    • 初始状态:对象没有锁标记。
    • 轻量级锁申请:第一个线程进入同步块时,JVM将锁的记录信息复制到线程的栈帧中,并将对象的标记状态设置为轻量级锁
    • 轻量级锁竞争:当其他线程尝试获取锁时,会使用CAS操作来竞争锁。如果CAS成功,表示获取锁成功,进入同步块;如果CAS失败,表示存在竞争,升级为重量级锁
public class LightweightLockExample {
    private static final Object lock = new Object();

    public static void main(String[] args) {
        synchronized (lock) {
            // 同步块
        }
    }
}
  • 重量级锁(Heavyweight Locking): 重量级锁是指当多个线程激烈竞争同步块时,JVM会将对象的锁升级为重量级锁,使用操作系统提供的互斥量来实现锁机制。重量级锁涉及到线程的阻塞和唤醒操作,开销较大。

  • 锁升级过程:

    • 初始状态:对象没有锁标记。
    • 重量级锁申请:当多个线程轮流竞争同步块时,锁会直接升级为重量级锁,通过操作系统提供的互斥量来实现锁机制。
public class HeavyweightLockExample {
    private static final Object lock = new Object();

    public static void main(String[] args) {
        synchronized (lock) {
            // 同步块
        }
    }
}

需要注意的是,锁升级的过程是由JVM自动完成的,开发人员无需显式地控制锁升级。JVM会根据同步竞争的情况来自动选择合适的锁级别,以提供更好的性能和效率。

结束语

大家可以针对自己薄弱的地方进行复习, 然后多总结,形成自己的理解,不要去背~

本着把自己知道的都告诉大家,如果本文对您有所帮助,点赞+关注鼓励一下呗~

相关文章

项目源码(源码已更新 欢迎star⭐️)

往期设计模式相关文章

设计模式项目源码(源码已更新 欢迎star⭐️)

Kafka 专题学习

项目源码(源码已更新 欢迎star⭐️)

ElasticSearch 专题学习

项目源码(源码已更新 欢迎star⭐️)

往期并发编程内容推荐

推荐 SpringBoot & SpringCloud (源码已更新 欢迎star⭐️)

博客(阅读体验较佳)