Java的原子类无锁并发利器详解

 更新时间:2023年12月22日 09:34:21   作者:Java面试365  
这篇文章主要介绍了Java的原子类无锁并发利器详解,原子类同样能够解决互斥性问题、原子性问题除此之外,因为原子类是无锁操作,没有用互斥锁解决带来的加锁解决性能消耗,这种绝佳方案是怎么做到的呢,需要的朋友可以参考下

前言引入

当存在如下场景,两个线程同时去将count值累加一万次,那么如下代码是否存在线程安全问题呢?

public class Test2 {
    public static void main(String[] args) throws InterruptedException {
        TestCount testCount = new TestCount();
        Thread T1 = new Thread(()->{
            testCount.add10k();
        });
        Thread T2 = new Thread(()->{
            testCount.add10k();
        });
        T1.start();
        T2.start();
        T1.join();
        T2.join();
        System.out.println(testCount.count);
    }
}
class TestCount{
    long count = 0;
    void add10k(){
        int idx = 0;
        while (idx++ < 10000){
            count += 1;
        }
    }
}

显然是存在的,最终的结果显然是小于两万的,原因是count值存在可见性问题,count+=1也存在原子性的问题。

一般思路都是将count采用volatile修饰,count+=1原子性问题采用synchronized互斥锁解决。

class TestCount{
    volatile long count = 0;
    synchronized void add10k(){
        int idx = 0;
        while (idx++ < 10000){
            count += 1;
        }
    }
}

但是对于这种简单的互斥操作,需要采用synchronized这种比较重的互斥锁吗?有没有更优的解决办法呢?当然存在,采用原子类无锁方案能够极大的提升性能

class TestCount{
    AtomicLong count = new AtomicLong(0);
     void add10k(){
        int idx = 0;
        while (idx++ < 10000){
            count.getAndIncrement();
        }
    }
}

原子类同样能够解决互斥性问题、原子性问题除此之外,因为原子类是无锁操作,没有用互斥锁解决带来的加锁解决性能消耗,这种绝佳方案是怎么做到的呢?

无锁方案实现原理

无锁方案之所以能够保证原子性,主要还是硬件保证,CPU为了解决并发问题,提供了CAS(Compare And Swap)指令即比较并交换,CAS一般包含三个参数,共享变量的内存地址A,用于比较的期望值B,更新共享变量C,当共享变量的内存地址A的值和共享变量B的值相等时,才将共享变量的内存地址A处的值更新为共享变量C。

将场景语义化如下

class SimpleCAS{
    int count;
    public synchronized int cas(int expect,int newCount){
        // 读取count值
       int oldcount = count;
       // 读取的count值和期望值比较
       if (oldcount == expect){
           count = newCount;
       }
       // 返回老值
       return oldcount;
    }
}

CAS指令判断并不是一次性的,如果比较失败又会重新取最新的值和期望值判断直到成功。

class SimpleCAS{
    volatile int count;
    public synchronized int cas(int expect,int newCount){
        // 读取count值
       int oldcount = count;
       // 读取的count值和期望值比较
       if (oldcount == expect){
            count = newCount;
        }
       // 返回老值
       return oldcount;
    }
    // 自旋操作,执行cas方法
    public void addOne(){
        int newCount = 0;
        do {
            newCount = count + 1;
        }while (count != cas(count,newCount));
    }
}

ABA问题

原子类虽然好用,但是一定需要的坑就是ABA问题,假如存在共享变量A值为5,线程T1将共享变量A的值改为2,而线程T2将共享变量A改为3,线程T3又将共享变量A改为2,那么对于线程T1来讲共享变量A是没有变的吗?显然不是,可能大多数场景我们并不关心ABA问题,对于基础数据递增可能认为值不变就够了,并不关心值是否已经修改,但是对于引用类型呢,这就一定要注意ABA问题,两个A虽然相等,但是属性可能已经发生变化。

原子类提供工具类解决ABA问题AtomicStampedReference和AtomicMarkableReference

public static void main(String[] args) throws InterruptedException {
    // 初始化原子类 定义初始引用和标识
    // AtomicMarkableReference同理可得,只是将版本戳换成了boolean类型
    AtomicStampedReference<String> reference = new AtomicStampedReference<>("zhangsan",1001);
    /**
      * expectedReference 期望的引用
      * newReference     新的引用
      * expectedStamp    期望的版本戳
      * newStamp         新的版本戳
      * 只有当期望引用和期望版本戳都符合实际版本戳和引用才能替换成功
    */
    reference.compareAndSet("zhangsan","lisi",1002,1003);
    // zhangsan 替换失败的原因是期望版本戳和实际版本戳不匹配
    System.out.println(reference.getReference());
    reference.compareAndSet("zhangsan","lisi",1001,1002);
    // lisi 替换成功
    System.out.println(reference.getReference());
    reference.compareAndSet("lisi1","wangwu",1002,1003);
    // lisi 替换失败的原因是期望引用和实际引用不匹配
    System.out.println(reference.getReference());
}

getAndIncrement源码分析

/**
  * this 指当前对象
  * valueOffset 指内存地址偏移量
*/
public final int getAndIncrement() {
    return unsafe.getAndAddInt(this, valueOffset, 1);
}
/**
  * this 指当前对象
  * valueOffset 指内存地址偏移量
*/
public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
        // 就是读取主内存的值
        var5 = this.getIntVolatile(var1, var2);
        // this.compareAndSwapInt方法就是对应上诉的cas方法,不过返回值是boolean类型
        // var5读取的主内存值
        // 更新成功返回true,跳出循环
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
    return var5;
}

原子工具类总览

原子工具类是一个大家族,根据使用可以分为原子化基本数据类型、原子化的对象引用类型、原子化数组、原子化对象属性更新器和原子化的累加器,方法都基本类似不需要刻意去记,需要用到的时候再来查就可以,但是需要有个印象,如下图所示。

image-20220302143728329

到此这篇关于Java的原子类无锁并发利器详解的文章就介绍到这了,更多相关原子类无锁并发利器内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java中的Semaphore源码分析

    Java中的Semaphore源码分析

    这篇文章主要介绍了Java中的Semaphore源码分析,Semaphore是一个访问公共资源的线程数量如限流、停车等,它是一个基于AQS实现的共享锁,主要是通过控制state变量来实现,需要的朋友可以参考下
    2023-11-11
  • SpringCloud配置客户端ConfigClient接入服务端

    SpringCloud配置客户端ConfigClient接入服务端

    这篇文章主要为大家介绍了SpringCloud配置客户端ConfigClient接入服务端,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-08-08
  • Spring Security中使用authorizeRequests遇到的问题小结

    Spring Security中使用authorizeRequests遇到的问题小结

    Spring 是非常流行和成功的 Java 应用开发框架,Spring Security 正是 Spring 家族中的成员,这篇文章主要介绍了Spring Security中使用authorizeRequests遇到的问题,需要的朋友可以参考下
    2023-02-02
  • java利用oss实现下载功能

    java利用oss实现下载功能

    这篇文章主要为大家详细介绍了java利用oss实现下载功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-10-10
  • idea前后跳转箭头的快捷键

    idea前后跳转箭头的快捷键

    这篇文章主要介绍了idea前后跳转箭头的快捷键,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-02-02
  • Java之Error与Exception的区别案例详解

    Java之Error与Exception的区别案例详解

    这篇文章主要介绍了Java之Error与Exception的区别案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-09-09
  • SpringBoot AOP Redis实现延时双删功能实战

    SpringBoot AOP Redis实现延时双删功能实战

    本文主要介绍了SpringBoot AOP Redis实现延时双删功能实战,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-08-08
  • Java基于对象流实现银行系统

    Java基于对象流实现银行系统

    这篇文章主要为大家详细介绍了Java基于对象流实现银行系统,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-09-09
  • Java编程简单应用

    Java编程简单应用

    本文主要介绍了三个简单Java小程序———1、HelloWorld(HelloWorld的来源);2、输出个人信息3、输出特殊图案。具有很好的参考价值,下面跟着小编一起来看下吧
    2017-02-02
  • 学习Java之File文件操作方法

    学习Java之File文件操作方法

    这篇文章我们主要学习如何实现IO流的具体操作,但很多时候,IO流都会操作一个文件,所以我们需要先学习在Java中如何操作文件,包括文件及文件夹的创建、遍历、删除等,有了文件操作的基础,我们才能更好地操作IO流,文中有详细的代码示例,需要的朋友可以参考下
    2023-09-09

最新评论