Java并发工具学习(四)——原子工具类

前言

前面三篇关于Java并发工具的学习总结完成了ThreadLocal,线程池和相关Lock接口实现类的一些总结,但是路漫漫其修远兮,Java中的并发征途还并没结束,这次总结一下原子工具类,这个在后面的并发容器的学习中,会有很多这个,需要提前总结一下

有哪几类?

什么是原子类这个问题,就不在这里说明了,无非就是可以保证在并发情况下可以保证对其变量的操作是线程安全的。原子类相比于锁有一定的优势。其锁的粒度更细,效率也比锁更高。

Java中原子类有哪几类,直接给出一张表格吧

类别 代表类型
基本类型的原子类 AtomicInteger
AtomicLong
AtomicBoolean
数组类型的原子类 AtomicIntegerArray
AtomicLongArray
AtomicReferenceArray
引用类型的原子类 AtomicReference
AtomicStampedReference
AtomicMarkableReference
升级类型的原子类 AtomicIntegerFieldUpdater
AtomicLongFieldUpdater
AtomicReferenceFieldUpdater
Adder累加器 LongAdder、DoubleAdder
Accumulator累加器 LongAccumulator
DoubleAccumulator

基本类型的原子类,就是包装了我们Java中8个基本类型的原子类。

数组类型的原子类,就是在这些数组中的每个元素都是基本的原子类型。

引用类型的原子类,就是针对对象引用的原子类型

升级类型的原子类,针对普通的字段类型的升级类型

Adder累加器,Java8中新引入的,Accumulator是在Adder的基础上新增了一些功能的累加器

基本类型

基本类型中只介绍一下AtomicInteger的基本使用,其中有如下方法

public final int get();//获取当前的值

public final int getAndSet(int newValue);//获取当前的值,并设置新值

public final int getAndIncrement();//获取当前的值,并自增

public final int getAndDecrement();//获取当前的值,并自减

boolean compareAndSet(int expect,int update);//如果输入的值等于预期值,则以原子方式将该值设置为输入值

实例代码,对比非原子类的问题

**
 * autor:liman
 * createtime:2021/11/15
 * comment:AtomicInteger的简单实例
 * AtomicInteger
 * AtomicLong
 * AtomicBoolean
 *AtomicInteger为例
 */
@Slf4j
public class AtomicIntegerDemo implements Runnable {
    
    

    private static final AtomicInteger atomicInteger = new AtomicInteger();

    private static volatile int basicCount = 0;

    public void incrementAtomicValue() {
    
    
        atomicInteger.getAndIncrement();
    }

    public void incrementBasicValue() {
    
    
        basicCount++;
    }

    @Override
    public void run() {
    
    
        for (int i = 0; i < 10000; i++) {
    
    
            incrementAtomicValue();
            incrementBasicValue();
        }
    }

    public static void main(String[] args) throws InterruptedException {
    
    
        AtomicIntegerDemo atomicIntegerDemo = new AtomicIntegerDemo();
        Thread threaOne = new Thread(atomicIntegerDemo);
        Thread threadTwo = new Thread(atomicIntegerDemo);
        threaOne.start();
        threadTwo.start();
        threaOne.join();
        threadTwo.join();
        System.out.println("原子类的结果:"+atomicInteger.get());
        System.out.println("普通类的结果:"+basicCount);
    }
}

运行结果

在这里插入图片描述

普通类存在线程安全问题,而原子类没有

数组类型

这里以AtomicIntegerArray为例,多个线程操作其中每个元素的加减

/**
 * autor:liman
 * createtime:2021/11/16
 * comment:AtomicIntegerArray的类型
 */
@Slf4j
public class AtomicArrayDemo {
    
    

    public static void main(String[] args) throws InterruptedException {
    
    
        //1000个AtomicInteger的数组集合
        AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(1000);
        Incrementer incrementer = new Incrementer(atomicIntegerArray);
        Decrementer decrementer = new Decrementer(atomicIntegerArray);
        //新建100个线程做加法,100个线程做减法
        Thread[] incrementThread = new Thread[100];
        Thread[] decrementThread = new Thread[100];
        for(int i=0;i<100;i++){
    
    
            incrementThread[i] = new Thread(incrementer);
            decrementThread[i] = new Thread(decrementer);
        }
        //依次启动每个线程
        Arrays.stream(incrementThread).forEach(Thread::start);
        Arrays.stream(decrementThread).forEach(Thread::start);
        //等待所有子线程运行结束
        for(int i=0;i<100;i++){
    
    
            incrementThread[i].join();
            decrementThread[i].join();
        }
        for(int i=0;i<atomicIntegerArray.length();i++){
    
    
            //如果数组中存在一个不是0的就判断为出现异常
            if(atomicIntegerArray.get(i)!=0){
    
    
                System.out.println("发现异常");
            }
        }
        System.out.println("运行结束");
    }

}

//对原子数组中的每个元素操作加法
class Decrementer implements Runnable {
    
    

    private AtomicIntegerArray array;

    public Decrementer(AtomicIntegerArray array) {
    
    
        this.array = array;
    }

    @Override
    public void run() {
    
    
        for(int i=0;i<array.length();i++){
    
    
            array.getAndDecrement(i);//对第i个元素减1
        }
    }
}

//对原子数组中的每个元素操作减法
class Incrementer implements Runnable {
    
    

    private AtomicIntegerArray array;

    public Incrementer(AtomicIntegerArray array) {
    
    
        this.array = array;
    }

    @Override
    public void run() {
    
    
        for(int i=0;i<array.length();i++){
    
    
            array.getAndIncrement(i);//对第i个元素减1
        }
    }
}

最后可以看到,这么复杂的并发情况,数据依旧正常。

引用类型

AtomicReference是可以让一个对象保证原子性,其用法也比较简单,和AtomicInteger类似。我们可以利用这个类,简单实现一个自旋锁

**
 * autor:liman
 * createtime:2021/11/14
 * comment:自旋锁简单实例
 */
public class SpinLockDemo {
    
    
	//锁还是要维持一个线程的引用,表示持有锁的线程
    private AtomicReference<Thread> sign = new AtomicReference<Thread>();

    //线程获取锁的方法
    public void lock(){
    
    
        Thread currentThread = Thread.currentThread();
        //通过cas的方式获取锁。获取锁也就是将当前sign设置为当前线程
        while(!sign.compareAndSet(null,currentThread)){
    
    
            System.out.println(currentThread.getName()+"获取锁失败,自旋中......");
        }
    }

    //释放锁的方式,直接cas的方式将sign属性置空即可,表示自己没有被任何线程持有
    public void unlock(){
    
    
        Thread currentThread = Thread.currentThread();
        sign.compareAndSet(currentThread,null);
    }

    public static void main(String[] args) {
    
    
        SpinLockDemo spinLock = new SpinLockDemo();
        Runnable runnable = new Runnable() {
    
    
            @Override
            public void run() {
    
    
                String currentThreadName = Thread.currentThread().getName();
                System.out.println(currentThreadName + "开始尝试获取自旋锁");
                spinLock.lock();
                System.out.println(currentThreadName + "获取自旋锁成功");
                try {
    
    
                    //模拟业务处理
                    Thread.sleep(30);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                } finally {
    
    
                    spinLock.unlock();
                    System.out.println(currentThreadName + "释放自旋锁成功");
                }
            }
        };
        Thread threadOne = new Thread(runnable);
        Thread threadTwo = new Thread(runnable);
        threadOne.start();
        threadTwo.start();
    }
}

普通变量升级为原子变量

Java中的原子类,除了已经提供的Atomic类变量和属性之外,还有相关工具可以将普通变量直接升级为原子变量,这个就需要用到Atomic中的FieldUpdater

还是以AtomicIntegerFieldUpdater为例进行说明

/**
 * autor:liman
 * createtime:2021/11/16
 * comment:将普通变量升级为原子变量
 */
public class AtomicIntegerFieldUpdateDemo implements Runnable{
    
    

    static Candidate tom;
    static Candidate peter;

    //构造的时候,第一个参数为Class,第二个为其中的属性名称
    public static AtomicIntegerFieldUpdater<Candidate> scoreUpdater
            =AtomicIntegerFieldUpdater.newUpdater(Candidate.class,"score");

    //用于示例,静态内部类
    public static class Candidate{
    
    
        volatile int score;
    }

    @Override
    public void run() {
    
    
        for(int i=0;i<10000;i++){
    
    
            peter.score++;
            //用原子变量的自增
            scoreUpdater.getAndIncrement(tom);
        }
    }

    public static void main(String[] args) throws InterruptedException {
    
    
        tom = new Candidate();
        peter = new Candidate();
        AtomicIntegerFieldUpdateDemo runnable = new AtomicIntegerFieldUpdateDemo();
        Thread threadOne = new Thread(runnable);
        Thread threadTwo = new Thread(runnable);
        threadOne.start();
        threadTwo.start();
        threadOne.join();
        threadTwo.join();
        System.out.println("普通的变量值:"+peter.score);
        System.out.println("升级为原子类的变量值:"+tom.score);
    }
}

实例比较简单,这里需要说明的是AtomicIntegerFieldUpdater.newUpdater方法,支持可以通过反射能直接访问到的属性,如果属性声明为private,则是不支持的。同时也不支持声明为static的属性,都是无法升级为原子变量的

Adder累加器

这个是Java8引入的,一个比较新的产物。在高并发的场景下LongAdder比AtomicLong的效率要高很多,通过这个实例,说明一下Adder的基本使用

使用AtomicLong来基数的实例

/**
 * autor:liman
 * createtime:2021/11/16
 */
@Slf4j
public class AtomicLongDemo {
    
    

    public static void main(String[] args) {
    
    
        AtomicLong atomicLongCounter = new AtomicLong(0);
        ExecutorService executorService = Executors.newFixedThreadPool(20);
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
    
    
            executorService.submit(new Task(atomicLongCounter));
        }
        //判断线程池是否停止
        executorService.shutdown();
        while(!executorService.isTerminated()){
    
    
            //线程池没有执行完成任务,主线程就空转,直到线程池运行结束
        }
        long endTime = System.currentTimeMillis();
        System.out.println("总耗时"+(endTime-startTime));
        System.out.println(atomicLongCounter.get());

    }

    private static class Task implements Runnable{
    
    

        private AtomicLong atomicLongCounter;

        public Task(AtomicLong atomicLongCounter) {
    
    
            this.atomicLongCounter = atomicLongCounter;
        }

        @Override
        public void run() {
    
    
            for(int i=0;i<10000;i++){
    
    
                atomicLongCounter.incrementAndGet();
            }
        }
    }
}

运行耗时:1229毫秒

在这里插入图片描述

同样的功能,用LongAdder来完成

/**
 * autor:liman
 * createtime:2021/11/16
 * comment:LongAdder累加器实例
 * LongAdder比AtomicLong效率要好
 */
@Slf4j
public class LongAdderDemo {
    
    

    public static void main(String[] args) {
    
    
        LongAdder longAdderCounter = new LongAdder();
        ExecutorService executorService = Executors.newFixedThreadPool(20);
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
    
    
            executorService.submit(new Task(longAdderCounter));
        }
        //判断线程池是否停止
        executorService.shutdown();
        while(!executorService.isTerminated()){
    
    
            //线程池没有执行完成任务,主线程就空转
        }
        long endTime = System.currentTimeMillis();
        System.out.println("总耗时"+(endTime-startTime));
        System.out.println(longAdderCounter.sum());

    }

    private static class Task implements Runnable{
    
    

        private LongAdder longAdderCounter;

        public Task(LongAdder longAdderCounter) {
    
    
            this.longAdderCounter = longAdderCounter;
        }

        @Override
        public void run() {
    
    
            for(int i=0;i<10000;i++){
    
    
                longAdderCounter.increment();
            }
        }
    }
}

在这里插入图片描述

耗时115毫秒

从原理层面来讲,AtomicLong每修改一次数据,都会将数据同步到共享内存中
在这里插入图片描述

而LongAdder则没有,LongAdder每个线程会有自己的计数器,只是在最后统计一次最终结果,将每个线程本地的计数值进行汇总,然后将最终结果同步到共享内存即可。这样的优化耗时很短。
在这里插入图片描述

Accumulator累加器

这个和LongAdder比较相似,但是更加通用,功能也稍微强一点。

简单的1~100的数值求和,这个是多线程的方式运行的,比传统的遍历来的要简单

/**
 * autor:liman
 * createtime:2021/11/16
 * comment:演示Accumulate累加器
 */
@Slf4j
public class LongAccumulatorDemo {
    
    

    public static void main(String[] args) {
    
    
        LongAccumulator accumulator = new LongAccumulator((x,y)->x+y,0);
        ExecutorService executorService = Executors.newFixedThreadPool(8);
        IntStream.rangeClosed(1,100).forEach(t->executorService.submit(()->accumulator.accumulate(t)));
        executorService.shutdown();
        //等待线程池结束,主线程自旋
        while(!executorService.isTerminated()){
    
    

        }
        System.out.println(accumulator.getThenReset());//这里输出1~100累加的结果,在多线程运行的情况下得到的结果
    }
}

总结

原子类的简单使用小结。下一篇CAS

猜你喜欢

转载自blog.csdn.net/liman65727/article/details/121410207