CAS解析和 synchronized 优化过程

目录

正文:

1.synchronized的优化过程

1.1锁粗化与锁细化

1.2自旋锁

1.3锁消除

1.4 偏向锁

1.5. 轻量级锁

1.6 重量级锁

 2.CAS

2.1概述

2.2java中的cas操作

 2.3ABA问题

总结:


正文:

1.synchronized的优化过程

synchronized 是 Java 中用于实现同步和线程安全的关键字。在 Java 早期版本中,synchronized 的实现较为简单,但它在面对高并发场景时存在一些性能问题。随着 Java 虚拟机(JVM)的发展,synchronized 经历了一系列的优化,以提高其在并发环境下的性能。

JVM 将 synchronized 锁分为 无锁、偏向锁、轻量级锁、重量级锁 状态。会根据情况,进行依次升级。

1.1锁粗化与锁细化

为了减少频繁的锁申请和释放操作,JVM 引入了锁粗化和锁细化技术。

  • 锁粗化:将多个连续的锁操作合并为一个更大范围的锁,减少锁的申请和释放次数。
  • 锁细化:将一个大范围的锁分解为多个小范围的锁,以减少锁的争用。
1.2自旋锁

为了减少线程在等待锁时的阻塞和上下文切换开销,JVM 引入了自旋锁。当一个线程尝试获取一个已被其他线程持有的锁时,它不是立即阻塞,而是在当前线程上执行一个循环(自旋),等待锁被释放。如果锁在短时间内被释放,自旋锁可以显著提高性能。同时JVM 会根据历史锁的获取情况来调整自旋的次数,这种自旋称为适应性自旋。如果一个锁通常在自旋后能够很快被获取,那么 JVM 会增加自旋的次数;反之,如果自旋很少成功,JVM 会减少自旋次数,避免浪费处理器资源。

1.3锁消除

JVM 通过逃逸分析,识别出一些不可能被其他线程访问的共享资源,从而在编译时消除对这些资源的锁操作。锁消除减少了不必要的同步开销,提高了程序的运行效率。

1.4 偏向锁

为了减少在没有锁竞争的情况下的同步开销,JVM 引入了偏向锁。偏向锁会偏向于第一个获取它的线程,这个线程在后续获取锁时不需要进行任何同步操作。只有当另一个线程尝试获取这个偏向锁时,偏向模式才会被撤销,并根据情况可能升级为轻量级锁或重量级锁。偏向锁不是真的 "加锁", 只是给对象头中做一个 "偏向锁的标记", 记录这个锁属于哪个线程. 如果后续没有其他线程来竞争该锁, 那么就不用进行其他同步操作了(避免了加锁解锁的开销) 如果后续有其他线程来竞争该锁(刚才已经在锁对象中记录了当前锁属于哪个线程了, 很容易识别当前 申请锁的线程是不是之前记录的线程), 那就取消原来的偏向锁状态, 进入一般的轻量级锁状态。

1.5. 轻量级锁

当偏向锁被打破,即有其他线程尝试获取这个锁时,JVM 会尝试使用轻量级锁。轻量级锁依赖于 CAS 操作来实现线程间的同步。如果 CAS 操作成功,线程获得锁;如果失败,线程可以自旋等待或者阻塞等待。自旋操作是一直让 CPU 空转, 比较浪费 CPU 资源。因此此处的自旋不会一直持续进行, 而是达到一定的时间/重试次数, 就不再自旋了,也就是所谓的 "自适应"。

1.6 重量级锁

如果竞争进一步激烈,自旋不能快速获取到锁状态, 就会膨胀为重量级锁 此处的重量级锁就是指用到内核提供的 mutex 。执行加锁操作,会先进入内核态, 在内核态判定当前锁是否已经被占用 ,如果该锁没有占用,则加锁成功,并切换回用户态。 如果该锁被占用,则加锁失败。 此时线程进入锁的等待队列,挂起,等待被操作系统唤醒.。经过一段时间后,这个锁被其他线程释放了,操作系统也想起了这个挂起的线程,于是唤醒 这个线程,尝试重新获取锁。

简单来说,会有以下几个过程:

 2.CAS

2.1概述

CAS(Compare-And-Swap)是一种乐观锁机制,用于实现对共享变量的原子操作。CAS 通过原子性地比较内存中的值和预期的值,当两者相等时,更新内存中的值为新值。CAS 操作包括三个步骤:读取变量当前值、比较变量当前值和期望值、更新变量值。如果当前值等于期望值,则更新变量值,否则重试。CAS 操作是在硬件层面上提供的原子操作,可以保证并发访问下的数据一致性。

CAS 操作包含三个参数:内存位置(V),预期原值(A)和新值(B)。CAS 的基本步骤如下:

  1. 读取内存位置 V 的当前值。
  2. 检查当前值是否与预期原值 A 相等。
  3. 如果相等,将内存位置 V 更新为新值 B。
  4. 如果不相等,操作失败,可以选择重试或放弃。

CAS 操作是原子的,这意味着在整个比较和交换的过程中,内存位置 V 不会被其他线程修改。

CAS伪代码(下面写的代码不是原子的, 真实的 CAS 是一个原子的硬件指令完成的)

boolean CAS(address, expectValue, swapValue) {
if (*address == expectedValue) {
   *address = swapValue; 
    return true;
 }
return false;
}
2.2java中的cas操作

Java 中的 CAS 操作是通过 java.util.concurrent.atomic 包中的原子类实现的。这些原子类利用了 CPU 指令集(如 CMPXCHG)中的原子操作来保证在多线程环境下变量的更新是“看起来”无锁的(lock-free)。Java 提供了一系列原子类,位于 java.util.concurrent.atomic 包中,如 AtomicIntegerAtomicLongAtomicReference 等。这些原子类提供了一些常见的原子操作方法,如 getAndIncrement()compareAndSet()getAndSet() 等。

下面以 AtomicInteger 类为例,介绍一些常用的原子操作方法:

  1. get():获取当前值。
  2. getAndIncrement():获取当前值并自增。
  3. compareAndSet(int expect, int update):比较当前值与期望值,如果相等则更新为新值。
  4. getAndSet(int newValue):设置新值并返回旧值。

下面是一个简单的示例代码,演示了如何使用 AtomicInteger 类进行原子操作:

import java.util.concurrent.atomic.AtomicInteger;

public class casExample {

    //原子变量
    private static AtomicInteger count = new AtomicInteger(0);


    public static void main(String[] args) throws InterruptedException {
        //创建两个线程
        Thread t1 = new Thread(() ->{
            for(int i = 0; i < 5000; i++){
                //类似与count++;
                count.getAndIncrement();
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                count.getAndIncrement();
            }
        });

        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(count);
    }
}

使用 AtomicInteger 类实现原子操作,在两个线程中分别对共享的 AtomicInteger 类型变量 count 进行自增操作,最终输出结果10000。

 2.3ABA问题

定义:

在多线程环境中,当线程 A 读取一个变量的值(假设为 A),在这个过程中线程 B 也读取了该值,并将变量修改为其他值(假设为 B),随后线程 B 又将变量改回原来的值 A。此时,线程 A 进行 CAS 操作,由于读取的值没有变化(仍然是 A),CAS 操作可能会成功,尽管实际上该值已经被其他线程修改过。

ABA 问题可能导致以下影响:

  • 数据不一致:由于 CAS 操作在值未变化的假象下成功,可能会导致基于该值的逻辑产生不一致的结果。
  • 逻辑错误:在某些情况下,ABA 问题可能导致程序逻辑错误,因为程序可能期望在值发生变化时执行某些操作,但由于 ABA 问题,这些操作可能被遗漏。

示例代码

import java.util.concurrent.atomic.AtomicReference;

public class test {
    public static void main(String[] args) throws InterruptedException {
        AtomicReference<Integer> ref = new AtomicReference<>(1);

        Thread t1 = new Thread(() -> {
            int original = ref.get();
            System.out.println("原始值为 " + original); // 输出原始值

            // 模拟值被其他线程修改并恢复
            ref.set(2);
            ref.set(original);

            // 即使 ref 的值恢复为 original,CAS 操作仍然会成功
            boolean success = ref.compareAndSet(original, 3);
            System.out.println("cas操作是否成功 " + success); // 输出 CAS 操作结果
        });

        t1.start();
        t1.join();
        System.out.println("cas操作后的值:" + ref);
    }
}

输出结果为

 

在这个例子中,主线程首先读取了 ref 的值为 1,然后模拟了其他线程将 ref 的值修改为 2 后又改回 1 的过程。即使 ref 的值被恢复了,由于 CAS 操作是基于值比较的,它仍然会认为操作成功,在特定的情况下就会造成问题。

解决方法:

有几种策略可以解决或缓解 ABA 问题:

  • 版本号:在每个对象中加入一个版本号,每次修改对象时,版本号递增。CAS 操作不仅比较对象的值,还要比较版本号,从而确保即使值相同,版本号不同也能检测到变化。
  • AtomicStampedReference:Java 提供了 AtomicStampedReference 类,它通过引入一个“stamp”(类似于版本号)来解决 ABA 问题。每次更新对象时,stamp 也会更新,从而避免了 ABA 问题。
  • 锁机制:虽然不是解决 ABA 问题的直接方法,但在某些情况下,使用锁(如 synchronized 或 ReentrantLock)来保证操作的原子性和可见性,也是一种可行的替代方案。

示例代码

import java.util.concurrent.atomic.AtomicStampedReference;

public class ABASolutionExample {
    public static void main(String[] args) {
        AtomicStampedReference<Integer> asr = new AtomicStampedReference<>(1, 1);

        int[] stamp = new int[1];
        stamp[0] = asr.getStamp();

        Integer current = asr.getReference();
        current = new Integer(2); // 模拟值被其他线程修改
        asr.compareAndSet(1, current, stamp[0], stamp[0] + 1); // 模拟恢复原始值

        stamp[0]++;
        boolean success = asr.compareAndSet(1, 3, stamp[0] - 1, stamp[0]);
        System.out.println("CAS operation success: " + success); // 输出 CAS 操作结果
    }
}

在这个例子中,即使 asr 的引用值被恢复为 1,但由于 stamp 值已经增加,CAS 操作将会失败。

总结:

锁策略在多线程编程中起着至关重要的作用,合理选择和优化锁策略可以提高程序的性能和可靠性。对于一些需要高性能的场景,想要保证线程安全如果单单靠加锁无法满足要求,可以考虑使用cas来进行优化。

  • 17
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值