incrementAndGet是如何实现线程安全的

本文深入探讨了线程安全的定义与实现,重点讲解了原子性在多线程环境中的作用,通过分析Atomic包中的incrementAndGet方法源码,解释了CAS操作如何确保线程安全。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

我们先来看一下线程安全性的定义.
定义:当多个线程访问某个类时.不管运行时环境采用何种调度方式或者这些进程将如何交替执行
,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的

线程安全主要体现在三个方面:

  • 原子性:提供了互斥访问,同一时刻只能有一个线程来对它进行操作

  • 可见性:一个线程对主内存的修该可以及时的被其他线程观察到

  • 有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序的存在,该观察结果一般会杂乱无章

我们今天主要来看原子性它是如何做到同一时刻只能有一个线程来对它进行操作的
提起原子性不得不介绍在jdk里提供的Atomic包

在以往的学习中,我们发现在不做线程同步的情况下,在高并发情况下进行计数器++会得不到我们想要的结果.所以我们今天来结合源码看看Atomic包是如何来给我们实现线程安全的.

我们先来看一个演示类

package com.imooc.concurrency.example.count;

import com.imooc.concurrency.annoations.ThreadSafe;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @Author zty
 * @Date 2020/3/9 下午6:45
 * @Description:
 */
@Slf4j
@ThreadSafe
public class CountExample2 {
    //请求总数
    public static int cilentTotal = 5000;

    //同时并发执行的线程数
    public static int threadTotal = 200;
    //这里要注意,不要使用int 要使用Atomic提供的类
    public static AtomicInteger count = new AtomicInteger(0);

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        //信号量
        final Semaphore semaphore = new Semaphore(threadTotal);

        final CountDownLatch countDownLatch = new CountDownLatch(cilentTotal);
        for (int i = 0; i < cilentTotal; i++){
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    add();
                    semaphore.release();
                } catch (InterruptedException e) {
                    log.error("exception",e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("count:{}",count.get());
    }

    private static void add(){
        count.incrementAndGet();
        //count.getAndIncrement();
    }
}

这是一个线程安全的计数器.
我们先介绍一下incrementAndGet和getAndIncrement的区别
两个方法处理的方式都是一样的,意思都是在当前值基础上+1,区别在于

getAndIncrement

方法是返回旧值(即加1前的原始值),而

incrementAndGet

返回的是新值(即加1后的值)

我们今天主要来看incrementAndGet方法是如何实现线程安全的

源码分析

/**
     * Atomically increments by one the current value.
     *
     * @return the updated value
     */
    public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }

在这个incrementAndGet里,我们可以看到使用了一个unsafe的类
unsafe里提供了一个getAndAddInt方法,这个方法不是非常,重要的是它的实现

public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
    }

我们来具体看一下他的实现,通过一个do while语句来做一个主体实现的
在while语句里核心调了一个方法叫做mpareAndSwapInt
我们打开看看它的实现

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

这是一个native标注的方法,这个代表是java底层的代码,不是我们通过java语句去实现的代码.

回过头来 我们继续看看getAndAddInt这个方法的调用

public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
    }

首先我们传过来的第一个值是当前的对象,比如说我们案例代码里的count,第二个值是我们当前的值(比如如果我们要实现2+1)那么var2 就是2 var4就是1
这里的var5,它是我们调用底层的方法var5 = this.getIntVolatile(var1, var2);获取底层当前的值
如果没有其他线程来处理count这个变量的时候,它的正常返回值应该是2,因此传到compareAndSwapInt的参数就是(count,2,2,2+1),这个方法想达到的目标就是对于count这个对象,如果当前的这个值和底层的这个值相等的情况下,就把它更新成后面那个值var5+var4

当我们一个方法进来的时候,我们var2的值是2,我们第一次取出来var5的值也等于2,但是当我们在执行更新成3的时候 也就是这句代码

while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

可能会被其它线程更改,所以我们要判断var2 是否与var5是相同的,只有是相同的,才允许它更新为3

通过这样不停的循环来判断,就能保证期望的值和底层的值相同

CAS比较与交换的伪代码可以表示为:

do{

备份旧数据;

基于旧数据构造新数据;

}while(!CAS( 内存地址,备份的旧数据,新数据 ))

Java中的乐观锁大部分都是基于CAS(Compare And Swap,比较和交换)操作实现的,CAS设一种原子操作,在对数据操作之前,首先会比较当前值跟传入值是否一样,如果一样咋更新,否则不执行更新操作直接返回失败状态。
compareAndSwapInt也是CAS的核心

在Java中,实现线程安全通常是为了防止多个线程同时访问共享资源导致的数据不一致或竞态条件。以下是几种常见的Java线程安全实践: 1. **同步(Synchronization)**:可以使用`synchronized`关键字或`java.util.concurrent.locks`包下的Lock接口来控制对共享资源的访问。通过将关键代码块包裹在`synchronized`代码块中,只有一个线程能进入该块。 ```java public class SharedResource { private int count = 0; public synchronized void increment() { count++; } } ``` 2. **原子变量(Atomic Variables)**:`java.util.concurrent.atomic`包提供了一系列原子类,如` AtomicInteger`,可以直接作为线程安全的数值变量使用,无需手动加锁。 ```java AtomicInteger counter = new AtomicInteger(0); counter.incrementAndGet(); ``` 3. **并发集合(Concurrent Collections)**:Java标准库提供了许多线程安全的集合类,比如`ConcurrentHashMap`、`CopyOnWriteArrayList`等,它们内部已经实现线程安全。 4. **使用 volatile 关键字**:标记一个变量为volatile可以保证其可见性和避免指令重排序带来的问题,适用于简单情况。 5. **使用 `ThreadLocal`**:每个线程有自己的副本,减少了数据竞争。 6. **使用 Lock 接口**:更精细的控制,提供了可中断、公平/非公平等特性,如`ReentrantLock`。 7. **分段锁(Segmented locking)**:对于大对象或自定义类型,可以考虑使用如`JDK8 中的 ConcurrentHashMap` 和 `LongAdder` 的分段锁定机制。 线程安全的关键在于理解何时以及如何在代码中正确地管理共享状态和同步,以防止竞态条件。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值