Java Review - 并发编程_原子操作类原理剖析

简介: Java Review - 并发编程_原子操作类原理剖析

195d03d17afc4a928bc581f313b01dfe.png

概述


JUC包提供了一系列的原子性操作类,这些类都是使用非阻塞算法CAS实现的,相比使用锁实现原子性操作这在性能上有很大提高。


由于原子性操作类的原理都大致相同,我们以AtomicLong类的实现原理为例,并探讨JDK8新增的 LongAdder和LongAccumulator类的原理


原子变量操作类


JUC并发包中包含有AtomicInteger、AtomicLong和AtomicBoolean等原子性操作类

9f846cfe40a8400c842a7320f7b4ddf7.png


AtomicLong是原子性递增或者递减类,其内部使用Unsafe来实现,我们看下面的代码

package java.util.concurrent.atomic;
import java.util.function.LongUnaryOperator;
import java.util.function.LongBinaryOperator;
import sun.misc.Unsafe;
/**
 * @since 1.5
 * @author Doug Lea
 */
public class AtomicLong extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 1927816293512124184L;
    // 1 获取Unsafe实例
    private static final Unsafe unsafe = Unsafe.getUnsafe();
  // 2 存放变量的偏移量
    private static final long valueOffset;
    /**
     * Records whether the underlying JVM supports lockless
     * compareAndSwap for longs. While the Unsafe.compareAndSwapLong
     * method works in either case, some constructions should be
     * handled at Java level to avoid locking user-visible locks.
     */
  // 3 判断JVM是否支持Long类型的CAS
    static final boolean VM_SUPPORTS_LONG_CAS = VMSupportsCS8();
    /**
     * Returns whether underlying JVM supports lockless CompareAndSet
     * for longs. Called only once and cached in VM_SUPPORTS_LONG_CAS.
     */
    private static native boolean VMSupportsCS8();
    static {
        try {
      // 4 获取value在AtomicLong中的偏移量
            valueOffset = unsafe.objectFieldOffset
                (AtomicLong.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }
  // 5  实际变量值
    private volatile long value;
    /**
     * Creates a new AtomicLong with the given initial value.
     *
     * @param initialValue the initial value
     */
    public AtomicLong(long initialValue) {
        value = initialValue;
    }
  .......
  .......
  .......
  .......
}



代码(1)通过Unsafe.getUnsafe()方法获取到Unsafe类的实例


为何能通过Unsafe.getUnsafe()方法获取到Unsafe类的实例?其实这是因为AtomicLong类也是在rt.jar包下面的,AtomicLong类就是通过BootStarp类加载器进行加载的。


代码(5)中的value被声明为volatile的,这是为了在多线程下保证内存可见性,value是具体存放计数的变量。


代码(2)(4)获取value变量在AtomicLong类中的偏移量。


主要方法


incrementAndGet 、decrementAndGet 、getAndIncrement、getAndDecrement


【JDK8+】

//(6)调用 Insafe方法,原子性设置 value值为原始值+1,返回值为递增后的值
  public final long incrementAndGet() {
    return unsafe.getAndAddLong(this, valueOffset, 1L) + 1L;
  }
//(7)调用 unsafe方法,原子性设置va1ue值为原始值-1,返回值为递减之后的值
 public final long decrementAndGet() {
        return unsafe.getAndAddLong(this, valueOffset, -1L) - 1L;
    }
//(8)调用 unsafe方法,原子性设置va1ue值为原始值+1,返回值为原始值
public final long getAndIncrement() {
        return unsafe.getAndAddLong(this, valueOffset, 1L);
    }
//(9)调用 unsafe方法,原子性设置va1ue值为原始值-1,返回值为原始值
  public final long getAndDecrement() {
        return unsafe.getAndAddLong(this, valueOffset, -1L);
    }

我们可以发现这几个方法内部都是通过调用Unsafe的getAndAddLong方法来实现操作,这个函数是个原子性操作。


第一个参数是AtomicLong实例的引用, 第二个参数是value变量在AtomicLong中的偏移值, 第三个参数是要设置的第二个变量的值 。


getAndIncrement方法在JDK 7中的实现逻辑为

public final long getAndIncrement(){
  while(true){
    long current=get();
    long next= current + 1;
    if (compareAndSet(current, next))
      return current
  }
}


在如上代码中,每个线程是先拿到变量的当前值(由于value是volatile变量,所以这里拿到的是最新的值),然后在工作内存中对其进行增1操作,而后使用CAS修改变量的值。如果设置失败,则循环继续尝试,直到设置成功。


而JDK 8中的逻辑为

unsafe.getAndAddLong(this, valueOffset, -1L);
public final long getAndAddLong(Object var1, long var2, long var4) {
        long var6;
        do {
            var6 = this.getLongVolatile(var1, var2);
        } while(!this.compareAndSwapLong(var1, var2, var6, var6 + var4));
        return var6;
    }

可以看到,JDK 7的AtomicLong中的循环逻辑已经被JDK 8中的原子操作类UNsafe内置了,之所以内置应该是考虑到这个函数在其他地方也会用到,而内置可以提高复用性。


boolean compareAndSet(long expect, long update)


    /**
     * 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(long expect, long update) {
        return unsafe.compareAndSwapLong(this, valueOffset, expect, update);
    }


我们可以看到内部还是调用了unsafe.compareAndSwapLong方法。如果原子变量中的value值等于expect,则使用update值更新该值并返回true,否则返回false。


小Demo

线程使用AtomicLong统计0的个数的例子

import java.util.concurrent.atomic.AtomicLong;
/**
 * @author 小工匠
 * @version 1.0
 * @description: TODO
 * @date 2021/11/30 22:52
 * @mark: show me the code , change the world
 */
public class AtomicLongTest {
    //(10)创建Long型原子计数器
    private static AtomicLong atomicLong = new AtomicLong();
    //(11)创建数据源
    private static Integer[] arrayOne = new Integer[]{0, 1, 2, 3, 0, 5, 6, 0, 56, 0};
    private static Integer[] arrayTwo = new Integer[]{10, 1, 2, 3, 0, 5, 6, 0, 56, 0};
    public static void main(String[] args) throws InterruptedException {
        //(12)线程one统计数组arrayOne中0的个数
        Thread threadOne = new Thread(() -> {
            int size = arrayOne.length;
            for (int i = 0; i < size; ++i) {
                if (arrayOne[i].intValue() == 0) {
                    atomicLong.incrementAndGet();
                }
            }
        });
        //(13)线程two统计数组arrayTwo中0的个数
        Thread threadTwo = new Thread(() -> {
            int size = arrayTwo.length;
            for (int i = 0; i < size; ++i) {
                if (arrayTwo[i].intValue() == 0) {
                    atomicLong.incrementAndGet();
                }
            }
        });
        //(14)启动子线程
        threadOne.start();
        threadTwo.start();
        //(15)等待线程执行完毕
        threadOne.join();
        threadTwo.join();
        System.out.println("count 0:" + atomicLong.get());
    }
}

642d94cb689a4d2dbe5aefca7b07f537.png


两个线程各自统计自己所持数据中0的个数,每当找到一个0就会调用AtomicLong的原子性递增方法


小结


在没有原子类的情况下,实现计数器需要使用一定的同步措施,比如使用synchronized关键字等,但是这些都是阻塞算法,对性能有一定损耗,而这里我们介绍的这些原子操作类都使用CAS非阻塞算法,性能更好。


但是在高并发情况下AtomicLong还会存在性能问题。JDK 8提供了一个在高并发下性能更好的LongAdder类,且听下篇分解

相关文章
|
1天前
|
Java 索引 容器
Java ArrayList扩容的原理
Java 的 `ArrayList` 是基于数组实现的动态集合。初始时,`ArrayList` 底层创建一个空数组 `elementData`,并设置 `size` 为 0。当首次添加元素时,会调用 `grow` 方法将数组扩容至默认容量 10。之后每次添加元素时,如果当前数组已满,则会再次调用 `grow` 方法进行扩容。扩容规则为:首次扩容至 10,后续扩容至原数组长度的 1.5 倍或根据实际需求扩容。例如,当需要一次性添加 100 个元素时,会直接扩容至 110 而不是 15。
Java ArrayList扩容的原理
|
5天前
|
存储 安全 Java
java.util的Collections类
Collections 类位于 java.util 包下,提供了许多有用的对象和方法,来简化java中集合的创建、处理和多线程管理。掌握此类将非常有助于提升开发效率和维护代码的简洁性,同时对于程序的稳定性和安全性有大有帮助。
32 17
|
1天前
|
存储 Java 程序员
Java基础的灵魂——Object类方法详解(社招面试不踩坑)
本文介绍了Java中`Object`类的几个重要方法,包括`toString`、`equals`、`hashCode`、`finalize`、`clone`、`getClass`、`notify`和`wait`。这些方法是面试中的常考点,掌握它们有助于理解Java对象的行为和实现多线程编程。作者通过具体示例和应用场景,详细解析了每个方法的作用和重写技巧,帮助读者更好地应对面试和技术开发。
19 4
|
2天前
|
Java 编译器 开发者
Java异常处理的最佳实践,涵盖理解异常类体系、选择合适的异常类型、提供详细异常信息、合理使用try-catch和finally语句、使用try-with-resources、记录异常信息等方面
本文探讨了Java异常处理的最佳实践,涵盖理解异常类体系、选择合适的异常类型、提供详细异常信息、合理使用try-catch和finally语句、使用try-with-resources、记录异常信息等方面,帮助开发者提高代码质量和程序的健壮性。
8 2
|
3天前
|
存储 缓存 安全
Java内存模型(JMM):深入理解并发编程的基石####
【10月更文挑战第29天】 本文作为一篇技术性文章,旨在深入探讨Java内存模型(JMM)的核心概念、工作原理及其在并发编程中的应用。我们将从JMM的基本定义出发,逐步剖析其如何通过happens-before原则、volatile关键字、synchronized关键字等机制,解决多线程环境下的数据可见性、原子性和有序性问题。不同于常规摘要的简述方式,本摘要将直接概述文章的核心内容,为读者提供一个清晰的学习路径。 ####
16 2
|
6天前
|
存储 安全 Java
如何保证 Java 类文件的安全性?
Java类文件的安全性可以通过多种方式保障,如使用数字签名验证类文件的完整性和来源,利用安全管理器和安全策略限制类文件的权限,以及通过加密技术保护类文件在传输过程中的安全。
|
5月前
|
Java C++
关于《Java并发编程之线程池十八问》的补充内容
【6月更文挑战第6天】关于《Java并发编程之线程池十八问》的补充内容
49 5
|
2月前
|
缓存 监控 Java
Java中的并发编程:理解并应用线程池
在Java的并发编程中,线程池是提高应用程序性能的关键工具。本文将深入探讨如何有效利用线程池来管理资源、提升效率和简化代码结构。我们将从基础概念出发,逐步介绍线程池的配置、使用场景以及最佳实践,帮助开发者更好地掌握并发编程的核心技巧。
|
4月前
|
安全 Java 开发者
Java中的并发编程:深入理解线程池
在Java的并发编程中,线程池是管理资源和任务执行的核心。本文将揭示线程池的内部机制,探讨如何高效利用这一工具来优化程序的性能与响应速度。通过具体案例分析,我们将学习如何根据不同的应用场景选择合适的线程池类型及其参数配置,以及如何避免常见的并发陷阱。
55 1
|
4月前
|
监控 Java
Java并发编程:深入理解线程池
在Java并发编程领域,线程池是提升应用性能和资源管理效率的关键工具。本文将深入探讨线程池的工作原理、核心参数配置以及使用场景,通过具体案例展示如何有效利用线程池优化多线程应用的性能。