单个线程中的Java代码执行是否保证是顺序的?

时间:2013-02-22 15:47:15

标签: java android c++ c

在Java,C和C ++中,源代码保证在一个给定的线程内逐行顺序执行,即使在编译器优化之后也是如此?如果允许系统重新排序你的代码似乎没有任何工作,但我似乎无法找到任何文件保证如果我在Java中有以下内容:

class MyClass{
String testString = "";

public MyClass(){

}

public void foo(){
    testString = "foo";
}
public void bar(){
    testString = "bar";
    testString += "r";
}
public String getTestString(){
    return testString;
}

}

class Main{
static void main(String[] args){
    MyClass testClass = new MyClass();
    testClass.foo();
    System.out.println(class.getTestString());
    testClass.bar();
    System.out.println(class.getTestString());
}
}

输出始终为

"foo"
"barr"

永远不会

"foo"
"rbar"
如果方法调用及其中的语句未按源代码中的指定顺序执行,则可能出现

或任何其他可能的变体。

这个问题特别针对Java而出现,因为它使程序员对目标系统上的字节码编译器和JIT编译器或解释器对代码的控制能力大大降低。我所讨论的主要系统是Android,为此我实现了自己的信号量和互斥锁定机制(例如,没有充分利用内置的Java并发机制,如'synchronized'和'volatile'关键字),这种机制更适合我的应用程序比Java提供的那些。然而,一位朋友警告我,由于Java从源代码到机器代码的多层次转换,除非我使用Java的内置并发机制,否则无法保证我的信号量和锁定实现将按照我的意图执行。这实际上归结为是否存在指定的保证,对于任何给定的运行时实现,代码的执行将在单个线程内顺序执行。所以主要问题是:

  1. 在C和C ++中,尽管编译器优化,代码执行仍然保证是顺序的吗?如果没有,是否禁用编译器优化足以实现这样的保证?

  2. 在Java中,尽管字节码编译器和JIT编译器或解释器(特别是在Android上运行,但也适用于任意VM实现)可能会发生变化,但代码执行仍保证是连续的?

  3. 如果上述答案符合我的预期,是否有任何编程语言/平台/上下文无法保证单个线程内的顺序执行?

6 个答案:

答案 0 :(得分:3)

如果只有一个线程,您的代码将具有直观的预期结果。任何优化都必须在优化之前保留功能。只有当你有多个线程时才会发生意外的事情。

Java语言规范基本上处理了存在多个线程时出现的违反直觉的行为,而您的问题是关于此处定义的“线程内语义”:

  

内存模型确定程序中每个点可以读取的值。隔离中每个线程的操作必须遵循该线程的语义,除了每次读取所看到的值由内存模型确定。当我们参考这个时,我们说该程序遵循线程内语义。 线程内语义是单线程程序的语义,允许根据线程内读取操作所看到的值完全预测线程的行为。确定线程的动作是否成立执行中的t是合法的,我们只是评估线程t的实现,因为它将在单线程上下文中执行,如本规范其余部分所定义。

http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html

答案 1 :(得分:2)

最重要的是,JIT /编译器更改指令以优化代码执行,使其不会更改单个给定线程中的代码执行结果。

在Java中,如果将代码放在一个sunchronized方法/块中,它可以保证不会重新排序指令。

您可以在本文http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#otherlanguages

中找到更多答案

答案 2 :(得分:2)

即使没有线程,大多数编译器也可以重新排序代码以获得最佳速度。但重新排序的结果不允许影响结果。在C / C ++中,这被称为as-if规则。

因此,在函数内,只要结果不受影响,就允许编译器重新排序以进行优化。所以我们在“源代码”和“生成代码”之间略有不同。

在线程代码中,单个线程会执行一个函数吗?这取决于你如何定义线程。什么是线程(一组堆栈帧/寄存器/和当前执行点)。

我认为在核心之间移动线程没有问题(虽然我可以看到为什么运行时不希望这样做我不认为它是不可行的)。所以你不能假设线程不会跳转核心。但是如果你认为“执行线程”是当前状态而没有引用任何硬件。

您可以保证的是,“执行线程”将从一段“生成的代码”的开头到结尾执行。它不会遗漏任何“生成的代码”。它将按顺序执行“生成的代码”。如果它是未安排的并重新安排,它将从它离开的地方继续。

答案 3 :(得分:1)

是的,您上面提供的代码将按顺序执行。即使有50个处理器,您的代码也会在一个线程上顺序执行。

顺便说一句,很确定你不能调用变量类。即使你可以,也不要。

答案 4 :(得分:1)

您的示例将按顺序执行,但如果操作数和其他表达式发挥作用,则会比此更复杂。这里描述了一般规则: http://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.7

请注意特定表达式评估顺序的最后一部分: http://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.7.5

答案 5 :(得分:1)

我认为简单的答案是编译器开发人员遵循“你不应该修改单线程程序的行为”的口头禅,除此之外,我认为你是独立的。

“预编程”对我所见过的乱序执行问题有一些最好的解释。如果有人知道我会知道一套更好的文章。以下是其中两个,但如果你真的想深入了解它,你应该在网站上查看更多资料:

Memory Ordering at Compile Time
Weak vs. Strong Memory Models