详细总结Java中常用的原子类

一、什么是原子类

Java中提供了一些原子类,原子类包装了一个变量,并且提供了一系列对变量进行原子性操作的方法。我们在多线程环境下对这些原子类进行操作时,不需要加锁,大大简化了并发编程的开发。

二、原子类的底层实现

目前Java中提供的原子类大部分底层使用了CAS锁(CompareAndSet自旋锁),如AtomicInteger、AtomicLong等;也有使用了分段锁+CAS锁的原子类,如LongAdder等。

三、常用的原子类

3.1 AtomicInteger与AtomicLong

AtomicInteger与AtomicLong的底层实现与用法基本相同,不同点在于AtomicInteger包装了一个Integer型变量,而AtomicLong包装了一个Long型变量。
AtomicInteger与AtomicLong的底层实现都使用了CAS锁。

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

/**
 * @author IT00ZYQ
 * @date 2021/5/24 15:33
 **/
public class T13_AtomicInteger {
    private static AtomicInteger atomicInteger = new AtomicInteger();
    private static AtomicLong atomicLong = new AtomicLong();
    private static Integer integer = 0;
    private static Long lon = 0L;
    public static void main(String[] args) {

        // 创建10个线程,分别对atomicInteger、atomicLong、integer、lon进行1000次增加1的操作
        // 如果操作是原子性的,那么正确结果 = 10 * 1000 = 10000
        Thread[] threads = new Thread[10];
        for (int i = 0; i < 10; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 1; j <= 1000; j++) {
                    atomicInteger.incrementAndGet();
                    atomicLong.incrementAndGet();
                    integer ++;
                    lon ++;
                }
            });
        }

        // 启动线程
        for (Thread thread : threads) {
            thread.start();
        }

        // 保证10个线程运行完成
        try {
            for (Thread thread : threads) {
                thread.join();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("AtomicInteger的结果:" + atomicInteger);
        System.out.println("AtomicLong的结果:" + atomicLong);
        System.out.println("Integer的结果:" + integer);
        System.out.println("Long的结果:" + lon);
    }
}

运行结果:

AtomicInteger的结果:10000
AtomicLong的结果:10000
Integer的结果:4880
Long的结果:4350
Process finished with exit code 0

多次运行发现原子类AtomicInteger与AtomicLong每次都能得到正确的结果10000,但是非原子类Integer与Long一般情况下都达不到10000,每次的结果也可能不一样。

3.2 LongAdder

LongAdder的底层实现使用了分段锁,每个段使用的锁是CAS锁,所以LongAdder的底层实现是分段锁+CAS锁。
在上面的程序添加了一个LongAdder变量进行测试

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAdder;

/**
 * @author IT00ZYQ
 * @date 2021/5/24 15:33
 **/
public class T13_AtomicInteger {
    private static AtomicInteger atomicInteger = new AtomicInteger();
    private static AtomicLong atomicLong = new AtomicLong();
    private static LongAdder longAdder = new LongAdder();
    private static Integer integer = 0;
    private static Long lon = 0L;
    public static void main(String[] args) {

        // 创建10个线程,分别对atomicInteger、atomicLong、integer、lon进行1000次增加1的操作
        // 如果操作是原子性的,那么正确结果 = 10 * 1000 = 10000
        Thread[] threads = new Thread[10];
        for (int i = 0; i < 10; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 1; j <= 1000; j++) {
                    atomicInteger.incrementAndGet();
                    atomicLong.incrementAndGet();
                    integer ++;
                    lon ++;
                    longAdder.increment();
                }
            });
        }

        // 启动线程
        for (Thread thread : threads) {
            thread.start();
        }

        // 保证10个线程运行完成
        try {
            for (Thread thread : threads) {
                thread.join();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("AtomicInteger的结果:" + atomicInteger);
        System.out.println("AtomicLong的结果:" + atomicLong);
        System.out.println("Integer的结果:" + integer);
        System.out.println("Long的结果:" + lon);
        System.out.println("LongAdder的结果:" + longAdder);
    }
}

运行结果:

AtomicInteger的结果:10000
AtomicLong的结果:10000
Integer的结果:6871
Long的结果:6518
LongAdder的结果:10000
Process finished with exit code 0

LongAdder类也是能够正确输出结果的。

四、原子类的性能测试

4.1 测试程序

package juc;

import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAdder;

/**
 * @author IT00ZYQ
 * @date 2021/5/24 15:51
 **/
public class T14_AtomicClassPerformance {
    private static AtomicLong atomicLong = new AtomicLong();
    private static LongAdder longAdder = new LongAdder();
    /**
     * 线程数
     */
    private static final int THREAD_COUNT = 100;
    /**
     * 每次线程循环操作次数
     */
    private static final int OPERATION_COUNT = 10000;

    public static void main(String[] args) {
        Thread[] threads = new Thread[THREAD_COUNT];

        // 创建对AtomicLong进行操作的线程
        for (int i = 0; i < THREAD_COUNT; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < OPERATION_COUNT; j++) {
                    atomicLong.incrementAndGet();
                }
            });
        }

        long start1 = System.currentTimeMillis();
        // 启动线程
        for (Thread thread : threads) {
            thread.start();
        }

        // 保证线程运行完成
        try {
            for (Thread thread : threads) {
                thread.join();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long end1 = System.currentTimeMillis();

        // 创建对LongAdder进行操作的线程
        for (int i = 0; i < THREAD_COUNT; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < OPERATION_COUNT; j++) {
                    longAdder.increment();
                }
            });
        }

        long start2 = System.currentTimeMillis();
        // 启动线程
        for (Thread thread : threads) {
            thread.start();
        }

        // 保证线程运行完成
        try {
            for (Thread thread : threads) {
                thread.join();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long end2 = System.currentTimeMillis();

        System.out.println("AtomicLong运行时间: " + (end1 - start1) + "ms, 运行结果:" + atomicLong);
        System.out.println("LongAdder运行时间: " + (end2 - start2) + "ms, 运行结果:" + longAdder);
    }

}

4.2 测试结果

THREAD_COUNT = 100, OPERATION_COUNT = 1000时的运行结果

AtomicLong运行时间: 40ms, 运行结果:100000
LongAdder运行时间: 57ms, 运行结果:100000
Process finished with exit code 0

THREAD_COUNT = 100, OPERATION_COUNT = 10000时的运行结果

AtomicLong运行时间: 108ms, 运行结果:1000000
LongAdder运行时间: 85ms, 运行结果:1000000
Process finished with exit code 0

THREAD_COUNT = 100, OPERATION_COUNT = 1000000时的运行结果

AtomicLong运行时间: 6909ms, 运行结果:100000000
LongAdder运行时间: 468ms, 运行结果:100000000
Process finished with exit code 0

THREAD_COUNT = 10, OPERATION_COUNT = 1000000时的运行结果

AtomicLong运行时间: 788ms, 运行结果:10000000
LongAdder运行时间: 162ms, 运行结果10000000
Process finished with exit code 0

4.3 结果分析

THREAD_COUNT * OPERATION_COUN足够小时,AtomicInteger的性能会略高于LongAdder,而随着THREAD_COUNT * OPERATION_COUN的增加,LongAdder的性能更高,THREAD_COUNT * OPERATION_COUN足够大时,LongAdder的性能远高于AtomicInteger。

4.4 底层实现分析

  • AtomicLong的原子性自增操作,是通过CAS实现的。在竞争线程数较少且每个线程的运行所需时间较短的情况下,这样做是合适的。但是如果线程竞争激烈,会造成大量线程在原地打转、不停尝试去修改值,但是老是发现值被修改了,于是继续自旋。 这样浪费了大量的CPU资源。
  • LongAdder在竞争激烈时,多个线程并不会一直自旋来修改值,而是采用了分段的思想,各个线程会分散累加到自己所对应的Cell[]数组的某一个数组对象元素中,而不会大家共用一个,把不同线程对应到不同的Cell中进行修改,降低了对临界资源的竞争。本质上,是用空间换时间。

到此这篇关于详细总结Java中常用的原子类的文章就介绍到这了,更多相关Java常用原子类内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 深入了解Java atomic原子类的使用方法和原理

    在讲atomic原子类之前先看一个小例子: public class UseAtomic { public static void main(String[] args) { AtomicInteger atomicInteger=new AtomicInteger(); for(int i=0;i<10;i++){ Thread t=new Thread(new AtomicTest(atomicInteger)); t.start(); try { t.join(0); } catch (I

  • Java concurrency之AtomicReference原子类_动力节点Java学院整理

    AtomicReference介绍和函数列表 AtomicReference是作用是对"对象"进行原子操作. AtomicReference函数列表 // 使用 null 初始值创建新的 AtomicReference. AtomicReference() // 使用给定的初始值创建新的 AtomicReference. AtomicReference(V initialValue) // 如果当前值 == 预期值,则以原子方式将该值设置为给定的更新值. boolean compare

  • Java concurrency之AtomicLong原子类_动力节点Java学院整理

    AtomicLong介绍和函数列表 AtomicLong是作用是对长整形进行原子操作. 在32位操作系统中,64位的long 和 double 变量由于会被JVM当作两个分离的32位来进行操作,所以不具有原子性.而使用AtomicLong能让long的操作保持原子型. AtomicLong函数列表 // 构造函数 AtomicLong() // 创建值为initialValue的AtomicLong对象 AtomicLong(long initialValue) // 以原子方式设置当前值为ne

  • java并发编程专题(十一)----(JUC原子类)数组类型详解

    上一节我们介绍过三个基本类型的原子类,这次我们来看一下数组类型: AtomicIntegerArray, AtomicLongArray, AtomicReferenceArray.其中前两个的使用方式差不多,AtomicReferenceArray因为他的参数为引用数组,所以跟前两个的使用方式有所不同. 1.AtomicLongArray介绍 对于AtomicLongArray, AtomicIntegerArray我们还是只介绍一个,另一个使用方式大同小异. 我们先来看看AtomicLong

  • Java concurrency之AtomicLongArray原子类_动力节点Java学院整理

    AtomicLongArray介绍和函数列表  AtomicLongArray函数列表 // 创建给定长度的新 AtomicLongArray. AtomicLongArray(int length) // 创建与给定数组具有相同长度的新 AtomicLongArray,并从给定数组复制其所有元素. AtomicLongArray(long[] array) // 以原子方式将给定值添加到索引 i 的元素. long addAndGet(int i, long delta) // 如果当前值 =

  • Java多线程Atomic包操作原子变量与原子类详解

    在阅读这篇文章之前,大家可以先看下<Java多线程atomic包介绍及使用方法>,了解atomic包的相关内容. 一.何谓Atomic? Atomic一词跟原子有点关系,后者曾被人认为是最小物质的单位.计算机中的Atomic是指不能分割成若干部分的意思.如果一段代码被认为是Atomic,则表示这段代码在执行过程中,是不能被中断的.通常来说,原子指令由硬件提供,供软件来实现原子方法(某个线程进入该方法后,就不会被中断,直到其执行完成) 在x86平台上,CPU提供了在指令执行期间对总线加锁的手段.

  • Java concurrency之AtomicLongFieldUpdater原子类_动力节点Java学院整理

    AtomicLongFieldUpdater介绍和函数列表 AtomicLongFieldUpdater可以对指定"类的 'volatile long'类型的成员"进行原子更新.它是基于反射原理实现的. AtomicLongFieldUpdater函数列表 // 受保护的无操作构造方法,供子类使用. protected AtomicLongFieldUpdater() // 以原子方式将给定值添加到此更新器管理的给定对象的字段的当前值. long addAndGet(T obj, lo

  • Java工具类之@RequestMapping注解

    一.前言 问题阐述:在某一场景下,我们的代码在 Service 实现相同,但却在 Controller 层访问时却希望不同的前缀可以访问.如下 :/say/hello.我们这里希望在不借助任何外部服务的情况下 通过 /a/say/hello 和 /b/say/hello 都可以访问到该接口,同时不想在 Controller 中写两个方法. @RestController @RequestMapping("say") public class SayController { @Autow

  • java并发编程专题(十)----(JUC原子类)基本类型详解

    这一节我们先来看一下基本类型: AtomicInteger, AtomicLong, AtomicBoolean.AtomicInteger和AtomicLong的使用方法差不多,AtomicBoolean因为比较简单所以方法比前两个都少,那我们这节主要挑AtomicLong来说,会使用一个,其余的大同小异. 1.原子操作与一般操作异同 我们在说原子操作之前为了有个对比为什么需要这些原子类而不是普通的基本数据类型就能满足我们的使用要求,那就不得不提原子操作不同的地方. 当你在操作一个普通变量时,

  • java基础之包装类的介绍及使用

    1. 包装类的介绍 针对八种基本数据类型定义相应的引用类型--包装类(封装类),有了类的热点后,就可以调用类中的方法 2. 基本数据类型 --> 包装类:调用包装类的构造器,如in1.toString() 使用场景:如果形参是一个object类类型的,必须要把基本类型转换成包装类才能传入到形参用 3. 包装类 --> 基本数据类型:调用包装类的xxxValue() 使用场景:包装类作为一个对象,不能做加减乘除运算的,必须转换成基本类型后才可以 但jdk5已经支持自动装箱和自动拆箱,所以不需要再

随机推荐