切换运算符是原子的吗?
Posted
技术标签:
【中文标题】切换运算符是原子的吗?【英文标题】:Is switch operator atomic? 【发布时间】:2017-06-14 22:07:32 【问题描述】:在文档中说你可以同样使用if-else
多次或switch-case
:
int condition;
setCondition(int condition)
this.condition = condition;
任一开关盒
switch (condition)
case 1: print("one"); break;
case 2: print("two"); break;
或
if (condition == 1) print("one");
else if (condition == 2) print("two");
接下来,condition
被声明为 volatile
并从多个线程调用方法 setCondition()
。
If-else
不是原子的,volatile
变量写入是同步操作。所以“one”和“two”字符串都可以打印在最后一个代码中。
如果使用了一些具有初始值的方法局部变量,则可以避免:
int localCondition = condition;
if (local condition == ..) ..
switch-case
操作员是否持有一些变量的初始副本?它是如何实现跨线程操作的?
【问题讨论】:
您的假设是错误的:If/else 保证即使在竞争条件下也只采用一个分支。这只有在竞争条件导致未定义行为的 c 等语言中才有可能。 @Borisif/else if
与 if/if
的全部意义在于事实并非如此。
@Voo 我明白你的意思 - 我很厚。把它归咎于周日的宿醉。我想如果值在评估期间发生变化,它可能会打印 nothing - 这是switch
会阻止的竞争条件。
在 which 文档中说明了多个 if-else 与 switch-case?也许它是一份非正式的文档,涵盖了语言基础知识,但不包括多线程的微妙之处
为保护@Voo 的宿醉,他断言“if/else
保证即使在竞争条件下[确切] 一个分支也会被采用”,这绝不是不真实的。后续地址 if/else if
与 if/if
,虽然可能是切线相关的,但不会改变声明的声明。
【参考方案1】:
来自Java specification on switch 语句:
执行 switch 语句时,首先计算表达式。 [...]
这建议表达式被评估一次,结果暂时保存在其他地方,因此不可能出现竞争条件。
但我在任何地方都找不到明确的答案。
快速测试表明情况确实如此:
public class Main
private static int i = 0;
public static void main(String[] args)
switch(sideEffect())
case 0:
System.out.println("0");
break;
case 1:
System.out.println("1");
break;
default:
System.out.println("something else");
System.out.println(i); //this prints 1
private static int sideEffect()
return i++;
确实,sideEffect() 只被调用一次。
【讨论】:
换个角度想:如果我们写switch(someFunctionWithSideEffect())
,那么switch语句最好只评估一次函数
@Todd Sewell "...表达式被评估..." 没有任何建议。这意味着表达式被评估 [根据 JVM 的规范],这意味着给定 JVM 的表达式评估器的实现者不得破坏易失性访问规则。因此,它应该受制于当前正在执行的线程的相同条件。
+1。奇怪的是,虽然规范确实定义了名词“读”和“写”等等,但并没有真正详细说明:docs.oracle.com/javase/specs/jls/se8/html/…。但除了long
和double
的特殊情况之外,它们似乎应该正是你天真地期望的意思;例如,似乎很明显,像int foo = this.mFoo; return foo + foo;
这样的东西只允许执行一个“读取”this.mFoo
。
我不是 Java 人,但这里的主题称为语言的正式memory model(如果有的话)。这些是语言对内存访问所做的保证,正如这里所指出的,对于给定并发性的正确性而言,这些保证变得很重要——并非必不可少。
@Glenn Slayden,java 有内存模型(参见 JMM),我认为问题是关于它的。但就 switch() 使用局部变量(评估表达式值)而言,这个问题似乎不在 JMM 范围内。请参阅 Shipilev 关于 JMM 的讲座:shipilev.net/blog/2014/jmm-pragmatics“内存模型似乎回答了一个简单的问题:“特定读取可以观察到哪些值?””【参考方案2】:
表达式在进入开关时被计算一次。
开关可以根据需要在内部使用结果多次以确定要跳转到的代码。类似于:
int switchValue = <some expression>;
if (switchValue == <some case>)
<do something>
else if (switchValue == <some other case>
<do something else>
// etc
其实一个switch会根据case的数量和值的类型,编译成多种字节码样式。
开关只需要计算一次表达式。
【讨论】:
如果表达式(和大小写标签)是字符串,这是真的吗? @carlos 是的。字符串是不可变的。一旦你引用了 switch 的 String 表达式,它实际上就是一个常量 @Bohemian 如果您的说法是“从 x 跟随 y”但 x 不正确,那么整个解释都是可疑的。是的,交换机只能有一个真正的分支,但那是因为 JLS 需要它,而不是因为涉及到跳转表(因为实际上绝大多数交换机都没有跳转表)。但是,是的,我删除了我的评论,因为我想让这个更清楚。一般来说,用实现细节解释合同行为是非常危险的(尤其是很多损坏的并发代码可以追溯到那个) 只是让我困惑的“开关只需要使用表达式一次”:表达式的值对Strings使用了两次,但只计算了一次...... @voo 很好...我已经将答案的实现换成了另一个(类似于switch
可以做的事情。以上是关于切换运算符是原子的吗?的主要内容,如果未能解决你的问题,请参考以下文章