线程安全之原子性Atomic(AtomicInteger|LongAdder|AtomicLong)

吃可爱长大的小学妹 提交于 2020-12-29 00:23:00

线程安全性

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

原子性

> 提供互斥访问,同一时刻只有一个线程对它进行访问.

Atomic包

> 位于java.util.concurrent.atomic,AtomicXXX : CAS、Unsafe.compareAndSwapXXX

> CAS(Compare and swap)比较和替换是设计并发算法用的的一项技术,比较和替换是用一个期望值和一个变量的当前值进行比较,如果变量的值和期望值相等,那么就用一个新值替换变量的值.

案例

> 线程安全

package com.keytech.task;

import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.LongAdder;

/**
 * @className: AtomicTest
 * @description: TODO 类描述
 * @author: mac
 * @date: 2020/12/27
 **/
public class AtomicTest {

    private static Integer clientTotal=5000;
    private static Integer threadTotal=200;

    private static AtomicInteger count=new AtomicInteger(0);

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        Semaphore semaphore= new Semaphore(threadTotal);
        for (int i = 0; i <clienttotal ; i++) { executorservice.execute(()->{
                try{
                    semaphore.acquire();
                    update();
                    semaphore.release();
                }catch (Exception e){
                    e.printStackTrace();
                }
            });
        }
        executorService.shutdown();
        System.out.println("count:"+count);
    }

    private static void update(){
        count.incrementAndGet();
    }
}

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;
}

> compareAndSwapInt(this, stateOffset, expect, update)这个方法的作用就是通过cas技术来预测stateOffset变量的初始值是否是expect,如果是,那么就把stateOffset变量的值变成update,如果不是,那么就一直自旋转,一直到stateOffset变量的初始值是expect,然后在在修改stateOffset变量的值变成update

LongAddr

>线程安全

package com.keytech.task;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.LongAdder;

/**
 * @className: LongAddrTest
 * @description: TODO 类描述
 * @author: mac
 * @date: 2020/12/28
 **/
public class LongAddrTest {

    private static Integer clientTotal=5000;
    private static Integer threadTotal=200;
    
    private static LongAdder count=new LongAdder();
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        Semaphore semaphore=new Semaphore(threadTotal);
        for (int i = 0; i &lt; clientTotal; i++) {
            try{
                semaphore.acquire();
                update();
                semaphore.release();

            }catch (Exception e){
                e.printStackTrace();
            }
        }

        executorService.shutdown();
        System.out.println("count"+count);

    }
    private static void update(){
        count.increment();
    }
}

AtomicLong

> 线程安全

package com.keytech.task;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicLong;

/**
 * @className: AtomicLongTest
 * @description: TODO 类描述
 * @author: mac
 * @date: 2020/12/28
 **/
public class AtomicLongTest {

    private static Integer clientTotal=5000;
    private static Integer threadTotal=200;
    private static AtomicLong count=new AtomicLong();

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        Semaphore semaphore=new Semaphore(threadTotal);
        for (int i = 0; i &lt; clientTotal; i++) {
            try{
                semaphore.tryAcquire();
                update();
                semaphore.release();

            }catch (Exception e){
                e.printStackTrace();
            }

        }
        executorService.shutdown();
        System.out.println("count"+count);
    }

    private static void update(){
        count.incrementAndGet();
    }
}

LongAddr与AtomicLong的区别

> AtomicLong的原理是依靠底层的cas来保障原子性的更新数据,在要添加或者减少的时候,会使用死循环不断地cas到特定的值,从而达到更新数据的目的。如果竞争不激烈,修改成功几率很高,否则失败概率很高,在失败几率很高的情况下,这些原子操作就会进行多次的循环操作尝试,因此性能会受到影响。

LongAdder

> 对于普通类型的Long和Doubble变量,JVM允许将64位的读操作或写操作拆成两个三十二位的操作。

> LongAdder则是内部维护一个Cells数组,每个Cell里面有一个初始值为0的long型变量,在同等并发量的情况下,争夺单个变量的线程会减少,这是变相的减少了争夺共享资源的并发量,另外多个线程在争夺同一个原子变量时候,如果失败并不是自旋CAS重试,而是尝试获取其他原子变量的锁,最后当获取当前值时候是把所有变量的值累加后再加上base的值返回的。

> LongAdder的核心是将热点数据分离,比如说它可以将AtomicLong内部核心数据value分离成一个数组,每个线程访问时,通过hash等算法,映射到其中一个数字进行计数,最终的计数结果则会这个数据的求和累加,其中热点数据value会被分离成多个cell,每个cell独自维护内部的值,当前对象实际值为所有cell累计合成,这样的话,热点就进行了有效的分离,并提高了并行度。

> LongAdder在AtomicLong的基础上将单点的更新压力分散到各个节点,在低并发的时候通过对base的直接更新可以很好的保障和AtomicLong的性能基本保持一致,而在高并发的时候通过分散提高了性能。缺点是LongAdder在统计的时候如果有并发更新,可能导致统计的数据有误差。

LongAddr与AtomicLong的使用场景

> 实际使用中,在处理高并发时,可以优先使用LongAdder,而不是继续使用AtomicLong,当然,在线程竞争很低的情况下,使用AtomicLong更简单更实际一些,并且效率会高些。其他情况下,比如序列号生成,这种情况下需要准确的数值,全局唯一的AtomicLong才是正确的选择,而不是LongAdder

</clienttotal>

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!