【问题标题】:Atomic Operations and multithreading原子操作和多线程
【发布时间】:2013-05-24 07:03:02
【问题描述】:

最近我正在阅读一个教程,其中我遇到了一个声明,上面写着..

"Java 语言规范保证读取或写入变量是原子操作(除非变量是longdouble 类型)。longdouble 类型的操作变量只有在以下情况下才是原子操作他们使用 volatile 关键字声明。”

AtomicIntegerAtomicLong 提供诸如 getAndDecrement()getAndIncrement()getAndSet() 之类的原子方法。

我对上面的陈述有点困惑。请您澄清何时使用 AtomicIntegerAtomicLong 类。

【问题讨论】:

    标签: java multithreading variables synchronization atomic


    【解决方案1】:

    执行a = 28aint)是一个原子操作。但是执行a++ 不是原子操作,因为它需要读取 a 的值、递增和写入结果的 a。因此,如果您使用a++ 来实现线程安全计数器,您可以让两个线程同时读取该值(例如 26),然后同时增加它并同时写入它,结果是 27,而不是 28。

    AtomicInteger 通过提供您列出的原子操作来解决此问题。在我的示例中,您将使用 incrementAndGet() 为例,这将保证最终值为 28 而不是 27。

    【讨论】:

    • 如果我们对“double”或“long”类型的变量执行操作,为什么还要使用“volatile”?
    • 如果使用 AtomicXxx,则不需要使用 volatile,因为 atomic 对象比 volatile 提供更多保证。对 volatile 整数执行 a++ 不会使操作成为原子操作。它只保证另一个线程在 a 增加后会看到它的新值。当为 long 或 double 值分配新值时,它还保证写入是原子操作(因为写入非易失性 long 或 double 变量包括写入 4 个字节,然后写入其他 4 个字节)。一般来说,原子对象优于 volatile 变量。
    • a = 28 如果a 是 long 或 double,则不能保证是原子的,除非它也是 volatile。
    • @assylias:是的。我没有重复这一点,因为它已经在问题中正确说明了。不过,我会进行编辑以澄清。
    • long 和 double 在 32 位 JVM 中不是原子的,因为它们是 8 个字节。它们在 64 位 JVM 中是原子的。
    【解决方案2】:

    原子意味着操作完成,没有任何事情发生的可能性。例如。 AtomicInteger 上的 getAndDecrement() 保证变量同时返回和递减。

    如果它不是原子操作,则值可能会递减(例如,从 3 到 2),然后被另一个线程修改(例如,将其从 2 更改为 5),然后返回为 5 .

    【讨论】:

      【解决方案3】:

      如果您需要读取变量并写入结果取决于读取的值,则需要AtomicInteger。例如,i++ 读取i(例如3)并写入i+1(例如4)。一个线程可能同时被中断,另外三个线程也增加i。现在我们回来了,i 实际上有 6 的值,但我们的线程仍然写入 4,基于​​它之前读取的内容。

      AtomicInteger.getAndIncrement 确保您不会被打断,因此始终会正确递增。此外,结果总是刷新到内存中,而非易失性i 可能不会刷新到内存中。在这种情况下,其他线程甚至可能看不到更改。

      【讨论】:

        【解决方案4】:

        当你改变一个变量时,操作的原子性是必需的。做int a = 10; 是一种原子操作,但它不会给你带来问题。给出操作的问题通常是像a++a = a + 2;这样的变异操作。

        Java 规范保证“读取”和“写入”是原子操作,而不是它们的组合。因此,按照规范,“读取、加 1 然后将结果写回”的操作不是原子的。此类操作称为复合操作,它们通常需要在我们的代码中使用时是原子的。

        原子类型有助于解决这个问题。在原子类型上使用 incrementAndget() 使“读取,加 1,然后将结果写回并读取新结果”成为线程安全上下文中的单个原子操作。

        希望这会有所帮助。顺便说一句,您应该阅读这篇 (http://walivi.wordpress.com/2013/08/24/concurrency-in-java-a-beginners-introduction/) 文章,了解并发和线程的基础知识。它很好地解释了这些东西。

        【讨论】:

          【解决方案5】:

          我认为这意味着长双读操作是原子的,而写操作是原子的。但是读+写不是原子的。

          volatile long num;
          num = num+1
          

          以上不是线程安全的。读和写是两个独立的操作。每一个都保证是原子的,但整个表达式不是。

          为了使其线程安全,您需要使用 AtomicLong 并使用 getAndIncrement 函数。

          【讨论】:

          • 它说的完全相反:在long a; a = 1;中,第二个语句不能保证是原子的。
          • 不,它没有 - “long 或 double 类型的操作变量只有在使用 volatile 关键字声明时才是原子的”,请注意 volatile
          • 您的第一句话具有误导性,没有提及 volatile。
          【解决方案6】:

          您使用 int 或 long 基于您正在处理的数字范围的上限/下限。请不要将 long 的非原子行为与 AtomicLong 混合。无论您在上面写什么都是正确的,但您可能混合了这两个概念。 AtomicXXX 在您进行“比较和设置”类型的操作时更有用。例如,即使 int 可以原子地修改/读取,以下代码在多线程环境中也会不正确:

          int i =10
          ..
          ..
          ..
          if(i == 10) i++;
          

          在多线程环境中,两个线程可以原子地访问此代码并更新 i 的值并使其处于一致状态。所以处理这种情况通常你保护代码“if(i == 10) i++;”带同步块。但是 AtomicInteger 类提供了 API 来实现这些事情,而无需使用较慢的同步块。 AtmoicLong API 也是如此

          【讨论】: