AtomicInteger的实际用途

Jam*_* P. 214 java concurrency atomic

我有点理解AtomicInteger和其他Atomic变量允许并发访问.在什么情况下这个类通常使用?

axt*_*avt 182

有两个主要用途AtomicInteger:

  • 作为原子计数器(incrementAndGet()等)可以被许多线程同时使用

  • 作为支持比较和交换指令(compareAndSet())实现非阻塞算法的原语.

    以下是BrianGöetz的Java Concurrency In Practice中的非阻塞随机数生成器示例:

    public class AtomicPseudoRandom extends PseudoRandom {
        private AtomicInteger seed;
        AtomicPseudoRandom(int seed) {
            this.seed = new AtomicInteger(seed);
        }
    
        public int nextInt(int n) {
            while (true) {
                int s = seed.get();
                int nextSeed = calculateNext(s);
                if (seed.compareAndSet(s, nextSeed)) {
                    int remainder = s % n;
                    return remainder > 0 ? remainder : remainder + n;
                }
            }
        }
        ...
    }
    
    Run Code Online (Sandbox Code Playgroud)

    正如您所看到的,它基本上与几乎相同incrementAndGet(),但执行任意计算(calculateNext())而不是增量(并在返回之前处理结果).

  • 你对第一次使用的理解是正确的 - 它只是确保如果另一个线程修改`read`和`write that value + 1`操作之间的计数器,这将被检测到而不是覆盖旧的更新(避免"丢失"更新"问题".这实际上是`compareAndSet`的特例 - 如果旧值为'2`,该类实际上调用`compareAndSet(2,3)` - 所以如果另一个线程同时修改了该值,则increment方法有效地重启从最开始. (8认同)
  • 我想我明白了第一个用途。这是为了确保在再次访问属性之前计数器已递增。正确的?您能举一个第二次使用的简短例子吗? (3认同)
  • "余数> 0?余数:余数+ n;" 在这个表达式中是否有理由在n为0时添加余数? (3认同)

And*_*yle 96

我能想到的绝对最简单的例子是增加原子操作.

标准整数:

private volatile int counter;

public int getNextUniqueIndex() {
    return counter++; // Not atomic, multiple threads could get the same result
}
Run Code Online (Sandbox Code Playgroud)

使用AtomicInteger:

private AtomicInteger counter;

public int getNextUniqueIndex() {
    return counter.getAndIncrement();
}
Run Code Online (Sandbox Code Playgroud)

后者是执行简单突变效果(特别是计数或唯一索引)的一种非常简单的方法,而不必求助于同步所有访问.

通过使用compareAndSet()乐观锁定类型可以使用更复杂的无同步逻辑- 获取当前值,基于此计算结果,如果值仍然是用于进行计算的输入,则设置此结果,否则重新开始 - 但是计数示例非常有用,AtomicIntegers如果有任何涉及多个线程的提示,我会经常使用计数和VM范围的唯一生成器,因为它们很容易使用我几乎认为使用普通的过早优化ints.

虽然你几乎总能用ints相应的synchronized声明来实现相同的同步保证,但美妙之AtomicInteger处在于线程安全性内置于实际对象本身,而不是你需要担心每种方法可能的交错和监视器.恰好访问该int值.在呼叫时getAndIncrement()比在返回i++和记住(或不记住)事先获取正确的监视器组时更容易违反线程安全.

  • 从我的角度来看,它主要是你用AtomicIntegers获得的封装 - 同步恰好发生在你需要的东西上,你得到公共API中的描述性方法来解释预期结果是什么.(加在一定程度上你是对的,往往是最终会简单地同步一个类中这很可能是过于粗粒度的所有方法,虽然有热点区域进行锁定的优化和反对过早优化的规则,我认为可读性是一个比表现更大的好处.) (3认同)
  • 谢谢你这个明确的解释.在方法全部同步的类上使用AtomicInteger会有什么好处?后者会被视为"更重"吗? (2认同)

Pow*_*ord 56

如果你看一下AtomicInteger的方法,你会发现它们倾向于对应于整数的常见操作.例如:

static AtomicInteger i;

// Later, in a thread
int current = i.incrementAndGet();
Run Code Online (Sandbox Code Playgroud)

是这个的线程安全版本:

static int i;

// Later, in a thread
int current = ++i;
Run Code Online (Sandbox Code Playgroud)

该方法映射是这样的:
++ii.incrementAndGet()
i++i.getAndIncrement()
--ii.decrementAndGet()
i--i.getAndDecrement()
i = xi.set(x)
x = ix = i.get()

还有其他便利方法,如compareAndSetaddAndGet


gab*_*uzo 36

主要用途AtomicInteger是当您处于多线程上下文中时,您需要在不使用的情况下对整数执行线程安全操作synchronized.原始类型的赋值和检索int已经是原子的,但是AtomicInteger带有许多非原子的操作int.

最简单的是getAndXXXxXXAndGet.例如getAndIncrement(),原子等价物i++不是原子的,因为它实际上是三个操作的捷径:检索,添加和赋值.compareAndSet对于实现信号量,锁,锁存等非常有用.

使用AtomicInteger比使用同步执行相同操作更快,更可读.

一个简单的测试:

public synchronized int incrementNotAtomic() {
    return notAtomic++;
}

public void performTestNotAtomic() {
    final long start = System.currentTimeMillis();
    for (int i = 0 ; i < NUM ; i++) {
        incrementNotAtomic();
    }
    System.out.println("Not atomic: "+(System.currentTimeMillis() - start));
}

public void performTestAtomic() {
    final long start = System.currentTimeMillis();
    for (int i = 0 ; i < NUM ; i++) {
        atomic.getAndIncrement();
    }
    System.out.println("Atomic: "+(System.currentTimeMillis() - start));
}
Run Code Online (Sandbox Code Playgroud)

在使用Java 1.6的PC上,原子测试在3秒内运行,而同步测试在大约5.5秒内运行.这里的问题是synchronize(notAtomic++)的操作非常短.因此,与操作相比,同步的成本非常重要.

除了原子性之外,AtomicInteger可以Integer用作例如Maps值的可变版本.

  • @Andrzej 当然,不是作为不可变的键,而是一个值。 (2认同)

Ser*_*nov 16

例如,我有一个库,可以生成某个类的实例.这些实例中的每一个都必须具有唯一的整数ID,因为这些实例表示发送到服务器的命令,并且每个命令必须具有唯一的ID.由于允许多个线程同时发送命令,因此我使用AtomicInteger生成这些ID.另一种方法是使用某种锁和常规整数,但这既慢又不优雅.


Dav*_*ann 7

就像gabuzo说的那样,当我想通过引用传递一个int时,我有时会使用AtomicIntegers.它是一个内置类,具有特定于体系结构的代码,因此它比我可以快速编写代码的任何MutableInteger更容易,也可能更优化.也就是说,这感觉就像滥用课堂一样.


小智 7

在Java 8中,原子类已经扩展了两个有趣的函数:

  • int getAndUpdate(IntUnaryOperator updateFunction)
  • int updateAndGet(IntUnaryOperator updateFunction)

两者都使用updateFunction来执行原子值的更新.区别在于第一个返回旧值而第二个返回新值.可以实现updateFunction以执行比标准操作更复杂的"比较和设置"操作.例如,它可以检查原子计数器是否低于零,通常需要同步,这里的代码是无锁的:

    public class Counter {

      private final AtomicInteger number;

      public Counter(int number) {
        this.number = new AtomicInteger(number);
      }

      /** @return true if still can decrease */
      public boolean dec() {
        // updateAndGet(fn) executed atomically:
        return number.updateAndGet(n -> (n > 0) ? n - 1 : n) > 0;
      }
    }
Run Code Online (Sandbox Code Playgroud)

代码取自Java Atomic Example.


Dgu*_*uel 5

当我需要给可以从多个线程访问或创建的对象赋予ID时,通常会使用AtomicInteger,并且通常将其用作我在对象的构造函数中访问的类的静态属性。