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

相关文章

  • 使用MyBatis查询千万级数据量操作实现

    使用MyBatis查询千万级数据量操作实现

    这篇文章主要为大家介绍了如何使用MyBatis 查询千万数据量的操作过程详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-05-05
  • 解决java中mybatis报错:org.apache.ibatis.binding.BindingException:Invalid bound statement(not found):xx问题

    解决java中mybatis报错:org.apache.ibatis.binding.BindingException:

    这篇文章主要介绍了解决java中mybatis报错:org.apache.ibatis.binding.BindingException:Invalid bound statement(not found):xx问题,具有很好的参考价值,希望对大家有所帮助
    2024-03-03
  • java仿QQ连连看游戏

    java仿QQ连连看游戏

    这篇文章主要为大家详细介绍了java仿QQ连连看游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-01-01
  • springAop实现权限管理数据校验操作日志的场景分析

    springAop实现权限管理数据校验操作日志的场景分析

    这篇文章主要介绍了springAop实现权限管理数据校验操作日志的场景分析,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-03-03
  • Java实现调用ElasticSearch API的示例详解

    Java实现调用ElasticSearch API的示例详解

    这篇文章主要为大家详细介绍了Java调用ElasticSearch API的效果资料,文中的示例代码讲解详细,具有一定的参考价值,感兴趣的可以了解一下
    2023-03-03
  • Java 必知必会的 URL 和 URLConnection使用

    Java 必知必会的 URL 和 URLConnection使用

    这篇文章主要介绍了Java 必知必会的 URL 和 URLConnection使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-10-10
  • 详解Jvm中时区设置方式

    详解Jvm中时区设置方式

    这篇文章主要介绍了详解Jvm中时区设置方式,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-09-09
  • 图解Spring容器中实例化bean的四种方式

    图解Spring容器中实例化bean的四种方式

    这篇文章主要介绍了图解Spring容器中实例化bean的四种方式,传统应用程序可以通过new和反射方式进行实例化Bean,而Spring IOC容器则需要根据 Bean 定义里的配置元数据,使用反射机制来创建Bean,需要的朋友可以参考下
    2023-11-11
  • Java与MySQL时间不一致问题解决

    Java与MySQL时间不一致问题解决

    本文主要介绍了Java与MySQL时间不一致问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-01-01
  • 强烈推荐 5 款好用的REST API工具(收藏)

    强烈推荐 5 款好用的REST API工具(收藏)

    市面上可用的 REST API 工具选项有很多,我们来看看其中一些开发人员最喜欢的工具。本文通过图文实例代码相结合给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧
    2020-12-12

最新评论