线程安全性--原子性---atomic

本文探讨了线程安全性的核心概念,包括原子性、可见性和有序性,重点讲解了Atomic包下的AtomicInteger类,剖析其incrementAndGet方法的实现原理,对比AtomicLong与LongAdder在高并发场景下的性能差异,以及AtomicBoolean的compareAndSet方法的应用。

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

线程安全性–原子性—atomic

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

线程安全性,主要体现在三个方面,分别是:

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

可见性:一个线程对主内存的修改可以同时被其他线程访问到

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

原子性就有必要提一下在jdk里面提供的atomic包,提供了很多atomic类

AtomicXXX:CAS、Unsafe.compareAndSwapInt

演示代码:

@ThreadSafe
public class CountExample2 {
    //请求总数
    private static int clientTotal=5000;

    //同时并发请求的线程数
    private static int threadTotal=200;

    public static AtomicInteger count=new AtomicInteger(0);
//    public static int count=0;

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore=new Semaphore(threadTotal);
        final CountDownLatch countDownLatch=new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
            executorService.execute(new Runnable() {
                public void run() {
                    try {
                        semaphore.acquire();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    add();
                    semaphore.release();
                    countDownLatch.countDown();
                }
            });
        }
        countDownLatch.await();
        executorService.shutdown();
//        System.out.println("count:"+count);
        System.out.println("count:"+count.get());
    }

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


}

两者区别:当需要返回值的时候两者会有区别,只是++操作的时候两者没有区别

IncrementAndGet:先做增加操作,在获取当前值

getAndIncrement:先获取当前值,在做增加操作

@ThreadSafe:线程安全

@NoThreadSafe:非线程安全的

为什么AtomicInteger是原子性,具体看看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…语句,在do…while…语句里面核心调用了一个方法compareAndSwapInt,这个方法需要特别注意,源码如下:

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

注:该方法使用native标注的方法,这个方法代表是java底层的方法,不是我们通过java来实现的,回过头来再来看看getAndAddInt方法的调用,第一个参数是Object,代表当前方法的对象,就是我们通过count传递过来的对象,第二个值是我们当前的值,比如我们要执行的是2+1=3这样的操作,第二个参数等于2,第三个参数等于1,这里的var5,等于调用底层getIntVolatile方法,而得到底层当前的值,如果没有别的线程访问的话,正常返回应该是2,那么应该传递到compareAndSwapInt这个方法的第一个参数应该是当前值应该是2,第二个参数是从底层传过来的2,那么var5 + var4就是从底层传过来的值加上1最后结果等于3,compareAndSwapInt这个方法需要达到的结果是。var1当前值,var2从底层传过来的值,var5 + var4最后是加上增加量1,就是最终的结果,总结就是如果当前对象和从底层传过来的值进行比较,如果相同的话,那么把它更新成var5 + var4这个值,当我们进来的时候第一个值是2,那么从底层传过来的值也应该是2,当我们进行更新成3的时候可能被其他线程更改,因此需要判断当前值和期望值是否相同,如果两个值相同的时候才被允许更新成最新值,否则继续while循环,继续判断,保证期望的值与底层的值完全相同时,才执行对应的加1的操作,把底层的值覆盖掉,compareAndSwapInt方法的核心就是我们所说的CAS的核心操作

AtomicLong、LongAdder

在JDK1.8中新增LongAdder和AtomicLong实现非常相似

public static LongAdder longAdder=new LongAdder();

LongAdder优缺点:在AtomicInteger中CAS底层实现是在一个死循环中不断尝试修改目标值,直到修改成功,如果竞争不激烈的时候修改成功的概率很高,某则的话修改失败的概率很高,在大量修改失败的情况下,CAS就会不断进行循环尝试,这对性能会有一定影响,对于普通对象的null,double对象,JVM允许将64位的读操作或写操作拆成两个32为的操作,那么LongAdder这个类,核心是将数据分离,比如atomiclong内部核心数据value分离成一个数组,哪个线程访问时通过hash等算法预测到其中一个数字进行计数,而最终的结果是这个数组的求和累加,其中热点数据value被分离成多个单元,每个单元独立维护各自的值,当前对象的实际值,由所有的单元累计合成,这样的话,数据被有效的分离,并提高的并行度,这样依赖LongAdder相当于在AtomicLong的基础上将单点的更新压力分散到各个节点上,在第并发的时候通过对base的直接更新可以很好的保障和atomic的性能基本一致,而在高并发的时候通过分散提高了性能,但是longadder也有自己的缺陷,在统计的时候如果有并发更新可能会导致统计的数据有误差,实际在使用时可以优先使用LongAdder而不是继续使用AtomicLong,当然在线程竞争很低的情况下进行计数,使用Atomic会更简单更直接一些,并且效率会更高一些,其他情况下,需要全局唯一的数据的时候,AtomicLong才是正确的选择

AtomicBoolean

/**
 * Atomically sets the value to the given updated value
 * if the current value {@code ==} the expected value.
 *
 * @param expect the expected value
 * @param update the new value
 * @return {@code true} if successful. False return indicates that
 * the actual value was not equal to the expected value.
 */
public final boolean compareAndSet(boolean expect, boolean update) {
    int e = expect ? 1 : 0;
    int u = update ? 1 : 0;
    return unsafe.compareAndSwapInt(this, valueOffset, e, u);
}

在类里有一个compareAndSet,在实际应用中如果希望某件事情只执行一次,在执行之前它的标记可能为false,一旦执行时候标记可能为true,通过调用compareAndSet方法分别传入false,true,就可以保证对应我们需要控制的代码只执行一次,甚至可以理解为当前只有一个线程可以执行这段代码,如果我们在就行完之后再把这段代码标记为false,那么这段代码可以继续执行,达到的效果就是这段代码同一时间是有一个线程可以执行

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值