2.CAS与锁相关

1. Java锁的分类

(1)悲观锁与乐观锁
(2)公平锁与非公平锁
(3)自旋锁与重入锁
(4)重量级锁与轻量级锁
(5)独占锁与共享锁

2.悲观锁与乐观锁

什么是悲观锁

在Mysql数据库修改一行数据就是悲观锁,悲观锁就是比较悲观,当多个线程对同一行数据进行修改的时候,最后只有一个线程能够修改成功,只要谁能够获取到行锁,则其他线程都不能对该数据进行修改,且是阻塞状态。
在Java角度,悲观锁如果没有获取到锁,则会阻塞等待,唤醒锁的成本较高。
在java中,Lock 和 synchronize锁都是悲观锁。

乐观锁

乐观锁比较乐观,认为不会有其他线程修改数据,通过预期或者版本号比较,如果不一致的情况则通过循环控制修改,线程不会被阻塞,效率较高。但是会消耗CPU资源。

Mysql层面实现乐观锁

(1)在表字段中增加一个version字段
(2)多个线程对同一行数据实现修改操作时,提前获取当前的version作为更新的判断依据
(3)带着verison编号去执行更新操作,如果version变了则修改失败
(4)修改失败就不断重试,直到成功

3.公平锁与非公平锁

公平锁

就是比较公平,根据请求锁的顺序排队,先来请求的线程就先获取锁,后来的就后获取,采用队列进行存放, 类似于吃饭排队,打水排队。

公平锁示例:

    public static void main(String[] args) {
    	//通过 ReentrantLock 创建一个公平锁,根据参数传递 true
        ReentrantLock reentrantLock = new ReentrantLock(true);
        //创建10个线程测试这个 获取锁的过程
        for (int i = 0; i < 10; i++) {
            int finalI = i;
            new Thread(() -> {
                try {
                    reentrantLock.lock();
                    System.out.println(Thread.currentThread().getName() + "--" + finalI);
                } catch (Exception e) {

                } finally {
                    if (reentrantLock != null) {
                        reentrantLock.unlock();
                    }
                }

            }).start();
        }
    }
}

运行结果:可以看到,获取锁的线程是有顺序的
在这里插入图片描述

非公平锁

顾名思义,不是根据请求顺序排列的,通过争抢的方式获取锁,哪个线程进来都先看一眼锁在不在,在就获取,不在再去排队等着抢。

非公平锁的效率比公平锁的效率要高,synchronized是非公平锁

非公平锁的示例:只需要把上述代码改成 false

    public static void main(String[] args) {
        ReentrantLock reentrantLock = new ReentrantLock(true);
        for (int i = 0; i < 10; i++) {
            int finalI = i;
            new Thread(() -> {
                try {
                    reentrantLock.lock();
                    System.out.println(Thread.currentThread().getName() + "--" + finalI);
                } catch (Exception e) {

                } finally {
                    if (reentrantLock != null) {
                        reentrantLock.unlock();
                    }
                }

            }).start();
        }
    }
}

运行结果:可以看到线程无序
在这里插入图片描述

公平锁的底层是如何实现的

通过队列的方式去实现 线程获取锁的排队,根据顺序去获取锁并执行逻辑。

4.锁的可重入性与自旋锁

什么是锁的可重入性

在同一个线程中锁是可以不断传递的,可以直接获取。

比如递归、A调用B这种情况,不可能等A把锁释放了再调用B吧,因为B是A方法的一部分,A也没有运行完。所以锁是线程级别的,它会记录是哪个线程获取了这个锁,然后那个线程就可以直接用这个锁去对应的方法直接用。

例如 synchronized、lock、aqs。

什么是CAS

CAS:Compare And Swap ,比较并交换,执行函数CAS(V,E,N)

CAS不是Java实现的,是由JNI调用C++实现的。

通常来讲,锁的存在导致 没有获取到锁的线程 会阻塞,为了解决这个问题,CAS的存在让线程不再阻塞,而是靠着一直自旋(循环运行)。

CAS的运行逻辑

CAS有三个操作数 ,内存值V(共享值),旧的期望值E(读取到的共享值),要修改的新值N
当且仅当预期值与内存值相同时,将内存值V修改为N,否则什么都不做,这个概念其实与乐观锁的类似。
当 E==V时,才会改变V。

CAS的优缺点

优点:通过循环的方式解决了 线程因为锁阻塞的问题
缺点:循环次数过高的话,会导致CPU标高,所以需要控制次数。

ABA问题

通过以上可以知道, 比较V和E时,我们如何知道 即使V值 和E 相同,但是已经变过了呢??

一般可以增加一个版本号version

<think>好的,用户想了解CAS乐观Java并发中的工作原理和实现方式。首先,我需要回忆一下CAS的基本概念。CAS全称是Compare-And-Swap,是一种无算法,用于实现多线程环境下的同步操作。它的核心思想是先比较内存中的值是否预期值相同,如果相同则更新为新值,否则不进行操作。 接下来,用户提到了Java中的实现,特别是Unsafe类和AtomicInteger。记得Unsafe类提供了硬件级别的原子操作,但一般不建议直接使用,因为它不安全。而AtomicInteger等原子类则是通过CAS来实现的,比如incrementAndGet方法。需要解释CAS的三个操作数:内存地址、预期值和新值。当执行CAS时,处理器会保证这个操作的原子性。 然后,要考虑到CAS可能存在的问题,比如ABA问题。这时候需要提到AtomicStampedReference或AtomicMarkableReference,它们通过引入版本号或标记来解决ABA问题。另外,自旋时间过长也是问题,可能导致CPU资源浪费,这时候可以结合适应性自旋或其他机制来优化。 用户提供的引用资料里提到了乐观和悲观的区别,这里需要对比一下。悲观认为并发冲突概率高,所以先加再操作,比如synchronized;而乐观假设冲突少,只在提交时检查,适用于读多写少的场景。 在Java中,CAS的实现依赖于Unsafe类的compareAndSwap方法,但具体到原子类如AtomicInteger,其内部实现会调用Unsafe的方法。例如,incrementAndGet()方法内部使用了一个循环,不断尝试CAS操作直到成功。 还要注意CAS的优缺点。优点是减少线程阻塞,提高并发性能;缺点则是ABA问题和自旋开销。需要举例说明,比如AtomicStampedReference的使用方法,如何通过版本号避免ABA问题。 最后,用户可能需要相关的问题来进一步学习,比如CASJava中的应用场景、ABA问题的具体案例、synchronized的对比等。这些相关问题可以帮助用户深入理解CAS机制。 总结回答的结构应该包括CAS的定义、工作原理、Java中的实现(原子类)、优缺点、问题及解决方案,以及相关问题的推荐。需要确保使用正确的中文术语,并正确引用提供的参考资料。</think>### CAS乐观的工作原理及Java实现 #### 一、CAS基本概念 CAS(Compare-And-Swap)是一种无并发控制技术,核心思想是通过**硬件级别的原子操作**实现线程安全。其操作包含三个关键参数: 1. **内存地址**(V) 2. **预期原值**(A) 3. **新值**(B) 当且仅当内存地址V中的值预期值A相等时,才会将V的值更新为B,否则不执行操作。整个过程由CPU指令(如x86的`CMPXCHG`)保证原子性[^2]。 #### 二、Java中的CAS实现 在Java中,CAS操作通过`sun.misc.Unsafe`类实现,但开发者通常直接使用`java.util.concurrent.atomic`包提供的原子类(如`AtomicInteger`): ```java // AtomicInteger的自增实现 public final int incrementAndGet() { return U.getAndAddInt(this, VALUE, 1) + 1; } ``` 底层通过**自旋循环**不断尝试CAS操作: ```java do { int prev = get(); // 读取当前值 int next = prev + 1; } while (!compareAndSet(prev, next)); // CAS尝试 ``` #### 三、CAS的三大问题解决方案 1. **ABA问题** - 现象:变量值从A→B→A,CAS无法感知中间变化 - 解决方案:使用`AtomicStampedReference`添加版本号 ```java AtomicStampedReference<Integer> ref = new AtomicStampedReference<>(100, 0); ref.compareAndSet(100, 200, 0, 1); // 需同时匹配值和版本号 ``` 2. **自旋开销大** - 冲突激烈时线程会长时间循环 - 优化策略:限制自旋次数或改用升级(如LongAdder的分段) 3. **单变量限制** - CAS只能保证单个变量的原子性 - 多变量场景需使用或`AtomicReference` #### 四、CAS的对比 | | 乐观CAS) | 悲观(synchronized) | |----------|----------------------------|---------------------------| | 实现方式 | 无,基于版本控制 | 有,基于互斥 | | 适用场景 | 读多写少(如计数器) | 写操作频繁 | | 性能特点 | 无上下文切换开销 | 上下文切换开销较大 | | 典型实现 | AtomicInteger | ReentrantLock | #### 五、实际应用场景 1. 原子类操作:`AtomicInteger`计数器 2. 非阻塞算法:`ConcurrentLinkedQueue` 3. 状态标记:AQS(AbstractQueuedSynchronizer)中的状态变更 ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值