Java原子变量类常见问题解决

 更新时间:2020年03月13日 10:51:41   作者:yuanyb  
这篇文章主要介绍了Java原子变量类常见问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

在学习多线程时,遇到了原子变量类,它是基于 CAS 和 volatile 实现的,能够保障对共享变量进行 read-modify-write 更新操作的原子性和可见性。于是我就写了一段代码试试,自认为非常正确。

public class Test{
  private static AtomicInteger ID = new AtomicInteger(0);
  public static int nextID(){ //返回的ID范围为 1~100
    if(ID.get() == 100) { //ID到达100时,则从1开始
      ID.set(1);
      return ID.get(); // return ID = 1;
    }
    else
      return ID.incrementAndGet(); //++ID
  }
  public static void main(String[] args) throws Exception{
    for(int i = 0; i < 5; i++){
      new Thread(()->{
        for(int j = 0; j < 100; j++)
          nextID();
      }).start();
    }
    Thread.sleep(1000); //应该输出100才对
    System.out.println(ID);
  }
}

用五个线程并发获得ID,每个线程获取100个,最后应该输出100才是,但试了好几次都不是100。原子变量类不是能保障原子性和可见性吗,为什么出现了竞态?

纠结了很久,还是很懵逼。后来发现 get 方法相当于读取一个 volatile 变量,而读取一个 volatile 变量时,不具备排他性!(AtomicInteger类内部使用了volatile修饰了value值,而volatile关键字不具备排他性)

也就是说,当一个线程刚读取到了共享的 volatile 变量的值时,其他线程可会马上对共享变量进行修改。如,线程A读取到ID的值为99时(还没对ID进行修改),其他线程可能马上就将ID加1了,此时共享变量为100了,其他线程再获取ID时,应该令ID=1才是,但线程A已经进入了else分支,它还认为ID=99,而不知道其他线程刚把ID加1变成了100,所以会吧ID加上1变成了101,这就出现了竞态。

《Java多线程编程实战指南 - 核心篇》中,作者说:“可见性的保障仅仅意味着一个线程能够读取到共享变量的相对新值,而不能保障该线程能读取到相应变量的最新值”。如volatile对可见性的保障就是保障的相对新值,由于volatile不具备排他性,所以有可能读线程刚读到一个相对新值,写线程就更改了共享变量,此时,读线程刚刚读取到的相对新值就不是最新的了。

作者对相对新值和最新值的定义:

对于同一个共享变量而言,一个线程更新了该变量的值之后,其他线程能够读取到这个更新后的值,那这个值就被称为该变量的 相对新值。

如果读取这个共享变量的线程在读取并使用该变量的时候其他线程无法更新该变量的值,那么该线程读取到的相对新值就被称为该变量的 最新值。需要加锁,才能读取到最新值。

解决办法,使用原子操作 compareAndSet:

private static int nextID(){ //返回的ID范围为 1~100
  ID.compareAndSet(100, 0);
  return ID.incrementAndGet();
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

相关文章

  • 详解使用IntelliJ IDEA 配置Maven(入门)

    详解使用IntelliJ IDEA 配置Maven(入门)

    本篇文章主要介绍了详解使用IntelliJ IDEA 配置Maven(入门),小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-11-11
  • eclipse创建springboot项目的三种方式总结

    eclipse创建springboot项目的三种方式总结

    这篇文章主要介绍了eclipse创建springboot项目的三种方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-07-07
  • Java之TreeSet和TreeMap的两种排序方式解读

    Java之TreeSet和TreeMap的两种排序方式解读

    这篇文章主要介绍了Java之TreeSet和TreeMap的两种排序方式解读,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-08-08
  • 解读Java中Set真的是无序的吗

    解读Java中Set真的是无序的吗

    这篇文章主要介绍了Java中Set是不是无序的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-07-07
  • SpringBoot深入浅出分析初始化器

    SpringBoot深入浅出分析初始化器

    这篇文章主要介绍了SpringBoot初始化器的分析,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-07-07
  • Maven包冲突导致NoSuchMethodError错误的解决办法

    Maven包冲突导致NoSuchMethodError错误的解决办法

    web 项目 能正常编译,运行时也正常启动,但执行到需要调用 org.codehaus.jackson 包中的某个方法时,产生运行异常,这篇文章主要介绍了Maven包冲突导致NoSuchMethodError错误的解决办法,需要的朋友可以参考下
    2024-05-05
  • 浅谈Java方法的重载

    浅谈Java方法的重载

    方法重载是指在一个类中定义多个同名的方法,但要求每个方法具有不同的参数的类型或参数的个数。调用重载方法时,Java编译器能通过检查调用的方法的参数类型和个数选择一个恰当的方法。方法重载通常用于创建完成一组任务相似但参数的类型或参数的个数不同的方法。
    2016-04-04
  • Java中使用byte[]获取16位字符串的技巧分享

    Java中使用byte[]获取16位字符串的技巧分享

    在Java编程中,处理字符编码和字节数组时,尤其是从​​byte[]​​​数组中获取字符串,可能会遇到字符集的复杂性问题,本文将介绍如何从一个​​byte[]​​数组中正确获取16位字符串,需要的朋友可以参考下
    2024-08-08
  • springboot中自定义异常以及定制异常界面实现过程解析

    springboot中自定义异常以及定制异常界面实现过程解析

    这篇文章主要介绍了springboot中自定义异常以及定制异常界面实现过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-09-09
  • Java将一个正整数分解质因数的代码

    Java将一个正整数分解质因数的代码

    这篇文章主要介绍了将一个正整数分解质因数。例如:输入90,打印出90=2*3*3*5,需要的朋友可以参考下
    2017-02-02

最新评论