Java学习笔记(十四)

synchronized

Synchronized的详细介绍:四种状态及升级过程

一、Synchronized概述

Java中的synchronized关键字是用于控制多线程对共享资源的访问,以保证线程安全的一种手段。它可以修饰方法或代码块,确保同一时刻只有一个线程能够执行被修饰的代码段。synchronized的实现基于JVM的内置锁机制,这种锁机制在不同的竞争状态下会表现出不同的行为,即锁的不同状态。

二、Synchronized的四种状态

Synchronized的锁状态随着线程对锁的竞争程度而逐渐升级,这些状态包括无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态。

1. 无锁状态

无锁状态是锁的初始状态,表示当前没有任何线程持有锁,对象头中的Mark Word分布为无锁状态标志。在这种状态下,多个线程可以并发访问共享资源,而不需要进行任何同步操作。无锁状态是性能最优的状态,因为它避免了锁的开销。

2. 偏向锁状态

偏向锁状态是锁的一种优化状态,旨在减少多线程对锁的竞争。当第一个线程获得锁时,锁会进入偏向锁状态,并将锁偏向该线程,即对象头中的Mark Word会记录持有锁的线程ID。这样,当该线程再次尝试获取锁时,只需要检查对象头的线程ID是否与自己相同,如果相同,则可以直接获得锁,而无需进行任何同步操作。

偏向锁的优势在于减少了锁的竞争和CAS操作,提高了性能。然而,当有其他线程尝试获取锁时,偏向锁会被撤销,并升级为轻量级锁或重量级锁。偏向锁的撤销和升级过程有一定的开销,因此,偏向锁适用于只有一个线程长期持有锁的情况。

3. 轻量级锁状态

轻量级锁状态是锁在竞争较为激烈时的一种状态。当有其他线程尝试获取已经被偏向锁持有的锁时,偏向锁会被撤销,并升级为轻量级锁。轻量级锁通过CAS(Compare-And-Swap)操作来实现,CAS操作是一种硬件级别的原子操作,它比较对象头中的Mark Word和线程栈中的期望值,如果相同,则将Mark Word更新为新的值,并返回成功;如果不相同,则说明有其他线程正在持有锁,CAS操作失败。

在轻量级锁状态下,线程会通过自旋的方式不断尝试获取锁,直到成功获取锁或自旋次数达到一定值(默认10次)为止。如果自旋成功,则线程获得锁,继续执行代码;如果自旋失败,则锁会升级为重量级锁。

轻量级锁的优势在于减少了线程的挂起和唤醒开销,提高了性能。然而,由于自旋操作会占用CPU资源,因此,轻量级锁适用于锁持有时间较短、线程竞争不激烈的情况。

4. 重量级锁状态

重量级锁状态是锁在竞争非常激烈时的一种状态。当轻量级锁的自旋次数达到一定值后,锁会升级为重量级锁。重量级锁通过操作系统的线程调度机制来实现,未获得锁的线程会被阻塞,并放入等待队列中,等待操作系统唤醒。

在重量级锁状态下,线程会进入阻塞状态,不再占用CPU资源。当持有锁的线程释放锁时,操作系统会唤醒等待队列中的一个线程,并使其获得锁,继续执行代码。重量级锁的优势在于避免了线程的自旋开销,但缺点是线程的挂起和唤醒开销较大,性能较低。

三、Synchronized的升级过程

Synchronized的升级过程是指锁的状态随着线程对锁的竞争程度而逐渐升级的过程。下面详细介绍Synchronized的升级过程:

1. 无锁状态到偏向锁状态的升级

当第一个线程尝试获取锁时,锁会从无锁状态升级为偏向锁状态。此时,对象头中的Mark Word会记录持有锁的线程ID,并将锁偏向该线程。如果后续没有其他线程尝试获取锁,则锁会一直保持偏向锁状态,线程可以直接获取锁而无需进行任何同步操作。

2. 偏向锁状态到轻量级锁状态的升级

当有其他线程尝试获取已经被偏向锁持有的锁时,偏向锁会被撤销,并升级为轻量级锁。撤销偏向锁的过程包括清除对象头中的线程ID和设置锁标志位为轻量级锁状态。然后,其他线程会通过CAS操作尝试获取锁。如果CAS操作成功,则线程获得锁;如果CAS操作失败,则说明有其他线程正在持有锁,此时线程会进入自旋状态,不断尝试获取锁。

在自旋过程中,线程会检查自旋次数是否达到阈值(默认10次)。如果自旋次数未达到阈值,则线程会继续自旋;如果自旋次数达到阈值,则锁会升级为重量级锁。需要注意的是,如果线程在自旋过程中成功获取了锁,则自旋次数会重置为0,以避免频繁升级锁状态带来的开销。

3. 轻量级锁状态到重量级锁状态的升级

当轻量级锁的自旋次数达到阈值后,锁会升级为重量级锁。升级过程包括将锁标志位设置为重量级锁状态、将线程放入等待队列中并阻塞该线程、唤醒等待队列中的一个线程并使其获得锁。

在重量级锁状态下,线程会进入阻塞状态并等待操作系统的唤醒。当持有锁的线程释放锁时,操作系统会唤醒等待队列中的一个线程,并使其获得锁。需要注意的是,由于重量级锁涉及到线程的挂起和唤醒操作,因此性能较低。因此,在可能的情况下,应尽量避免使用重量级锁。

四、Synchronized的优化策略

除了上述的锁升级过程外,synchronized还提供了一些优化策略来提高性能。这些优化策略包括锁粗化、锁消除和偏向锁的批量重偏向等。

1. 锁粗化

锁粗化是指将多个细粒度的锁合并为一个粗粒度的锁,以减少锁的竞争和开销。例如,在一个循环中,如果每次迭代都需要获取和释放锁,则会导致大量的锁竞争和开销。为了避免这种情况,可以将循环外部的锁扩展到循环内部,使整个循环只获取一次锁,从而减少锁的竞争和开销。

锁粗化的优点在于减少了锁的竞争和开销,提高了性能。然而,需要注意的是,锁粗化可能会导致更大的锁粒度,从而增加死锁的风险。因此,在使用锁粗化时,需要权衡锁的粒度和死锁的风险。

2. 锁消除

锁消除是指通过编译器或JVM的优化技术来消除不必要的锁操作。例如,在一个只读的代码块中,如果多个线程同时访问同一个对象而不会修改该对象的状态,则可以使用锁消除来避免不必要的锁操作。

锁消除的优点在于减少了不必要的锁操作,提高了性能。然而,需要注意的是,锁消除可能会增加代码的可读性和维护性方面的挑战。因此,在使用锁消除时,需要权衡性能的提升和代码的可读性、维护性之间的关系。

3. 偏向锁的批量重偏向

偏向锁的批量重偏向是指当某个类型的对象频繁发生偏向锁的撤销时,JVM会将这些对象的偏向锁重新偏向到新的加锁线程上,以减少偏向锁的撤销和升级带来的开销。例如,当一个类型的对象被多个线程频繁访问时,如果每个线程都只访问该对象的一小部分数据,则会导致大量的偏向锁撤销和升级。为了避免这种情况,JVM会将这些对象的偏向锁重新偏向到新的加锁线程上,从而减少偏向锁的撤销和升级带来的开销。

偏向锁的批量重偏向的优点在于减少了偏向锁的撤销和升级带来的开销,提高了性能。然而,需要注意的是,批量重偏向可能会导致偏向锁的灵活性降低。因此,在使用批量重偏向时,需要权衡性能的提升和偏向锁的灵活性之间的关系。

五、总结

Synchronized是Java中用于控制多线程对共享资源的访问的一种手段。它通过内置的锁机制来实现线程同步,保证线程安全。Synchronized的锁状态包括无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态。这些状态随着线程对锁的竞争程度而逐渐升级。在升级过程中,锁会经历从无锁状态到偏向锁状态、再到轻量级锁状态、最后到重量级锁状态的过程。除了锁升级过程外,Synchronized还提供了一些优化策略来提高性能,包括锁粗化、锁消除和偏向锁的批量重偏向等。这些优化策略旨在减少不必要的锁操作、降低锁的竞争和开销,从而提高性能。

在使用Synchronized时,需要根据具体的场景选择合适的锁状态和优化策略。例如,在只有一个线程长期持有锁的情况下,可以使用偏向锁来提高性能;在锁持有时间较短、线程竞争不激烈的情况下,可以使用轻量级锁来减少线程的挂起和唤醒开销;在锁竞争非常激烈的情况下,可以使用重量级锁来保证线程安全。同时,还需要注意避免死锁和活锁等问题,以确保程序的正确性和稳定性。

当轻量级锁的自旋次数达到阈值后,锁会升级为重量级锁 这个轻量级锁指的是哪一个锁?自旋次数又是谁在记录?不是等待线程自旋吗?轻量级锁不是被持有线程持有吗,为什么是锁自旋?

一、轻量级锁的含义

在Java中,轻量级锁是JVM(Java虚拟机)内部实现的一种锁机制,旨在优化同步代码块的执行效率,减少传统重量级锁(如使用synchronized关键字或ReentrantLock)所带来的性能开销。轻量级锁主要适用于锁竞争不激烈、持有锁时间较短的场景。

当提到“当轻量级锁的自旋次数达到阈值后,锁会升级为重量级锁”时,这里的“轻量级锁”指的是当前正在被某个线程尝试获取但尚未成功获取到的锁,且该锁当前处于轻量级锁状态。也就是说,这个锁原本是一个轻量级锁,但由于线程竞争或持有锁时间过长等原因,它可能会升级为重量级锁。

二、自旋次数的记录

自旋次数是由JVM内部的一个计数器来记录的。当线程尝试获取轻量级锁时,如果锁已经被其他线程持有,那么该线程会进入自旋状态,不断尝试获取锁。在这个过程中,JVM会用一个计数器来记录线程的自旋次数。如果自旋次数达到了JVM设定的阈值(如默认10次),但线程仍然未能获取到锁,那么JVM会将锁升级为重量级锁。

三、自旋的主体与轻量级锁的持有

关于自旋的主体,确实是等待获取锁的线程在进行自旋。这里的“锁自旋”并不是指锁本身在进行自旋,而是指等待获取锁的线程在不断尝试获取锁的过程中进行的自旋。

而轻量级锁在被持有线程持有期间,是处于锁定状态的。这意味着其他线程无法直接获取该锁,而需要等待持有线程释放锁后才能尝试获取。在等待期间,如果其他线程尝试获取锁,它们会进入自旋状态,不断尝试获取锁,直到成功获取或自旋次数达到阈值为止。

四、轻量级锁升级为重量级锁的原因

轻量级锁升级为重量级锁的原因主要有两个:

  1. 线程竞争激烈:当多个线程同时竞争同一个锁时,轻量级锁的自旋操作可能会频繁发生,导致CPU资源的浪费。为了避免这种情况,JVM会将锁升级为重量级锁,使未获得锁的线程进入阻塞状态,从而释放CPU资源。
  2. 锁持有时间过长:如果持有锁的线程长时间不释放锁,那么等待获取锁的线程可能会一直进行自旋操作,导致性能下降。为了避免这种情况,JVM也会在自旋次数达到一定阈值后将锁升级为重量级锁。

综上所述,轻量级锁的自旋次数是由JVM内部的一个计数器来记录的,等待获取锁的线程会进行自旋操作以尝试获取锁。当自旋次数达到阈值后,JVM会将锁升级为重量级锁以优化性能。

CAS

CAS(Compare-And-Swap)操作是一种用于实现无锁编程的原子操作,在Java并发编程中得到了广泛应用。以下是对CAS操作的详细介绍及举例:

一、CAS操作的基本概念

CAS操作包含三个操作数:内存位置(V)、预期原值(A)和新值(B)。其执行过程是:当且仅当内存位置V的当前值等于预期原值A时,才将内存位置V的值更新为新值B,否则不进行任何操作。这个操作是原子的,意味着它一旦开始执行,就不能被其他线程打断,必须等待它执行完成。

二、CAS操作的工作原理

CAS操作的工作原理可以概括为“比较并交换”。具体来说,当线程尝试更新某个共享变量的值时,它会先读取该变量的当前值,并将其保存在一个局部变量中(即预期原值A)。然后,线程会使用CAS操作来比较内存中的值是否仍然等于它之前读取的值(即预期原值A)。如果相等,说明没有其他线程修改过这个值,线程就可以安全地将新值写入内存。如果不相等,说明有其他线程已经修改了这个值,线程就会重新读取内存中的值,并再次尝试进行CAS操作,直到成功为止。

三、CAS操作的应用场景

CAS操作通常用于多线程环境中,以确保在没有使用传统锁的情况下,能够安全地更新共享数据。它广泛应用于各种并发数据结构和算法中,如原子类(AtomicInteger、AtomicLong等)、自旋锁、无锁队列等。

四、CAS操作的优缺点

  • 优点

    1. 无锁设计:CAS操作允许无锁的线程安全编程,这可以减少线程竞争和锁的开销。
    2. 高性能:在某些情况下,CAS操作比使用锁具有更高的性能,特别是在高并发环境下。
    3. 避免死锁:由于没有使用锁,因此使用CAS可以避免死锁问题。
  • 缺点

    1. ABA问题:由于CAS检查的是值而不是内存地址,如果一个值在被读取和比较期间被修改并又恢复了原值(即发生了ABA情况),CAS操作可能会错误地认为值没有变化。
    2. 循环延迟:在高竞争环境下,CAS操作可能会导致线程不断自旋尝试更新值,这可能导致CPU资源的浪费。

五、CAS操作的举例

以下是一个使用CAS操作实现原子类AtomicInteger的示例:

import java.util.concurrent.atomic.AtomicInteger;

public class CASExample {
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(0);

        // 使用CAS操作实现原子性的自增
        System.out.println(atomicInteger.getAndIncrement()); // 输出0,并将值更新为1
        int val = atomicInteger.get(); // 获取当前值,输出1
        System.out.println(val);

        // 伪代码解释CAS操作过程
        // 假设当前值为0,预期原值oldValue也为0
        // while (true) {
        //     currentValue = memoryValue(); // 读取当前内存值
        //     if (currentValue == oldValue) {
        //         if (compareAndSwap(currentValue, newValue)) {
        //             break; // 更新成功,跳出循环
        //         }
        //     } else {
        //         oldValue = currentValue; // 更新预期原值,继续循环
        //     }
        // }
    }
}

在这个示例中,AtomicInteger类的getAndIncrement方法使用了CAS操作来实现原子性的自增。它首先读取当前值,然后在一个循环中不断尝试使用CAS操作来更新值,直到成功为止。这个过程确保了即使在高并发环境下,也能保证自增操作的原子性。

综上所述,CAS操作是一种重要的并发编程技术,它允许我们在不使用传统锁的情况下实现线程安全的更新操作。然而,它也存在一些缺点和限制,需要我们在实际应用中根据具体场景进行权衡和选择。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

路上阡陌

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

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

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

打赏作者

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

抵扣说明:

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

余额充值