Java中可以有原子枚举吗?

9

今天我看到了一个关于AtomicEnum的实用类,但我想知道为什么Java标准库中没有包含原子枚举。事实上,我非常怀疑它是否真正是原子性的,以及这个实用类是否有效。

AtomicEnum类是这里

  1. 我该如何检查它是否可以原子操作?
  2. 是否有工具可以查看编译后的代码,并确信它只执行了一条机器指令?
  3. 是否可能从代码中发现它是否原子化?
  4. 因此,如果这个原子枚举有效,我可以拥有一个属性AtomicEnum,并且可以在不使用volatile关键字和同步getter和setter的情况下安全地使用它吗?

1
请尝试寻找文件的镜像,因为链接已失效。 - vbence
不确定这是否为原始文件,但我找到了这个链接:https://github.com/stratosphere/stratosphere/blob/master/stratosphere-runtime/src/main/java/eu/stratosphere/nephele/util/AtomicEnum.java - Amit Goldstein
4个回答

24

今天我看到了这个 AtomicEnum 的实用类,但我不知道为什么它不包含在Java标准库中,如果有一个原子性的枚举,它是否可能存在?

你提供的 AtomicEnum 类只是对 AtomicReference 类做了一层封装,它可以给任何 Object 提供并发访问。

如果你需要多个线程同时获取和设置枚举值,那么一个 volatile enumField 就足够了。使用 volatile 关键字可以确保一个线程所做的更新操作能被其他线程看到。

如果你需要执行类似 compareAndSet(...) 的原子操作 - 即测试字段是否具有特定值,如果是则进行更新,那么你需要使用 AtomicReference。你可以使用如下代码来实现:

private final AtomicReference<MyEnum> atomicColor = new AtomicReference<>();
...
atomicColor.set(ColorEnum.RED);
...
if (atomicColor.compareAndSet(ColorEnum.RED, ColorEnum.GREEN)) {
   ...
}
...
ColorEnum color = atomicColor.get();

我该如何检查它是否原子地执行操作?

AtomicReferencejava.util.concurrent 类库的一部分,已经经过了充分的测试。

有没有工具可以查看编译后的代码,确保它只是一条机器指令?

这并不是必要的。如果您想知道它在做什么,可以查看 AtomicReference 的源代码。不幸的是,真正的魔力在于 sun.misc.Unsafe 的本地代码中。

能否从代码中发现它?

这并不是必要的。

如果此原子枚举(Atomic enum)起作用,那么我可以有一个 AtomicEnum 属性,并且可以在没有 volatile 关键字和同步 getter 和 setter 的情况下安全使用它吗?

是的,AtomicReference 包装了一个 volatile V value,因此您不必这样做。


1
谢谢。所以,如果我使用原子引用包装任何对象,它将完全线程安全,包括其所有属性吗?或者这只适用于像枚举这样的不可变对象?因此,我可以确信,如果我用原子枚举替换枚举,没有对象会锁定我的类在这个例子中?http://stackoverflow.com/questions/20685372 - Johnny
3
不行,它只保护引用。如果你想要一个可变对象,那么你需要更严格地进行同步。 - Tim B
1
枚举默认是线程安全的。AtomicReference 使您的字段原子化,因此多个线程可以原子地测试和更改它以不同的枚举。@Johnny - Gray
4
@Gray提到了一个很好的点:“默认情况下”,通过向枚举添加可变字段,您可以很容易地“破坏”其线程安全性!不要这样做。请注意保持枚举的不可变性。 - Joachim Sauer
3
我们使用volatile的原因是为了进行内存同步。如果一个线程更改了共享的enum,其他线程不一定能看到更新。我们使用AtomicReference的原因是可以原子性地执行 compareAndSet(...) 操作。如果您不需要原子方法(即只设置或获取它),那么一个volatile enumField就足够了。但是如果有多个线程,您需要使用volatile。@Johnny。 - Gray
显示剩余5条评论

3
这个AtomicEnum只是一个轻量级的包装器,封装了一个AtomicReference。这是有道理的,因为enum值本质上就是一个对象(引用)。

所以,如果AtomicReference正常工作(我认为我们可以假设它会),AtomicEnum也将正常工作。


既然枚举类型具有唯一的整数标识符(“序数”),那么使用AtomicInteger会更好一些,不是吗? - android developer
2
@androiddeveloper:你可以这样做,但我真的看不出有什么优势。存储序数值不安全(可能会存储超出范围的值),需要更多的工作来存储/恢复(映射到/从实际枚举类型),并且在大多数平台上占用的存储空间是相同的(对于32位平台肯定如此,通常即使在64位平台上也是如此)。 - Joachim Sauer

1
引用的访问是原子性的,但对象的访问不是。因此,对引用值的更改将正确地在所有线程中传播,但对实际枚举对象的更改可能不会。
示例...
enum Weekdays {
    public int rank = 0;
    MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY;
}

案例1:
AtomicEnum<Weekdays> today = Weekdays.TUESDAY;

每个共享'today'的线程都将有一个对Weekdays.TUESDAY的引用。
情况2:
today.rank = 3;

不能保证每个线程都有更新后的排名。(排名不是易失性的。每个人在缓存中维护着对象的单独副本。)

因此,引用访问是原子的,但对象访问不是。
注意:
1)原子访问并不意味着在一条机器指令中执行。但从逻辑上可以这样想。虽然它是不可分割的,但它可以包含多条指令。
2)即使存在这样的工具,测试它也会非常困难,因为更新过程几乎是不可预测的。因此,可能会出现误报结果。

希望这可以帮助你。


你提出了一个很好的观点,我之前不知道“原子访问并不意味着在一条机器指令中执行”。谢谢! - Johnny
如果您有AtomicEnum(或任何AtomicReference),访问实际对象的唯一方法是通过volatile字段或某些更重的同步,因此情况2也不是问题。即“实际枚举对象的更改可能不会”是错误的。但这混淆了原子操作之间的差异(通过定义,对int的写入是原子的)和可见性保证。您可以拥有不提供任何可见性保证的原子操作,尽管这很少见。 - Voo
你说得没错,但如果你没有使用volatile字段或者一些更重的同步(就像在给定的情况下),“实际枚举对象的更改可能不可见”。我只是试图保持用例简单,避免复杂的罕见情况。 - Tanmay Patil
1
如果你只是直接使用枚举,或者你指的是AtomicReference<SomeEnum>版本,那么这就很令人困惑了。在第一种情况下,你是正确的,但在第二种情况下,你可以确保获得必要的可见性保证。对我来说,它读起来好像你在第二种情况下也在谈论AtomicReference(或者更准确地说是AtomicEnum),你可能只需要重新表达一下。 - Voo
它在这两种情况下都是AtomicEnum。所以第一种情况没问题,对吧?在第二种情况中,没有对引用本身进行更改,它所指向的对象正在被更改。因此,没有可见性保证。如果两个核的L1缓存中有两份对象副本,则无法确保它们将全局更新。 - Tanmay Patil

0

我认为写这样的类没有任何意义。事实上,如果您正在编写此类,您需要重新考虑类的责任。 ENUM绝对是线程安全的,所以我不确定为什么有人需要AtominEnum,或者它只是某个类是原子性的并且还使用了一些枚举,但是将其命名为AtomicEnum非常令人困惑,不利于设计或命名约定。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接