Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/391.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 为什么我是++;不是原子弹?_Java_Multithreading_Concurrency - Fatal编程技术网

Java 为什么我是++;不是原子弹?

Java 为什么我是++;不是原子弹?,java,multithreading,concurrency,Java,Multithreading,Concurrency,为什么i++在Java中不是原子的 为了更深入地了解Java,我尝试计算线程中循环的执行频率 所以我用了 private static int total = 0; 在主课上 我有两条线 线程1:PrintsSystem.out.println(“来自线程1的你好!”) 线程2:PrintsSystem.out.println(“来自线程2的你好!”) 我数一数线程1和线程2打印的行数。但是线程1的行数+线程2的行数与打印出来的总行数不匹配 这是我的密码: import java.util

为什么
i++
在Java中不是原子的

为了更深入地了解Java,我尝试计算线程中循环的执行频率

所以我用了

private static int total = 0;
在主课上

我有两条线

  • 线程1:Prints
    System.out.println(“来自线程1的你好!”)
  • 线程2:Prints
    System.out.println(“来自线程2的你好!”)
我数一数线程1和线程2打印的行数。但是线程1的行数+线程2的行数与打印出来的总行数不匹配

这是我的密码:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Test {

    private static int total = 0;
    private static int countT1 = 0;
    private static int countT2 = 0;
    private boolean run = true;

    public Test() {
        ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
        newCachedThreadPool.execute(t1);
        newCachedThreadPool.execute(t2);
        try {
            Thread.sleep(1000);
        }
        catch (InterruptedException ex) {
            Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
        }
        run = false;
        try {
            Thread.sleep(1000);
        }
        catch (InterruptedException ex) {
            Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
        }
        System.out.println((countT1 + countT2 + " == " + total));
    }

    private Runnable t1 = new Runnable() {
        @Override
        public void run() {
            while (run) {
                total++;
                countT1++;
                System.out.println("Hello #" + countT1 + " from Thread 2! Total hello: " + total);
            }
        }
    };

    private Runnable t2 = new Runnable() {
        @Override
        public void run() {
            while (run) {
                total++;
                countT2++;
                System.out.println("Hello #" + countT2 + " from Thread 2! Total hello: " + total);
            }
        }
    };

    public static void main(String[] args) {
        new Test();
    }
}

在JVM中,增量包括读取和写入,因此它不是原子的

i++
涉及两个操作:

  • 读取
    i的当前值
  • 增加值并将其分配给
    i
  • 当两个线程同时对同一个变量执行
    i++
    时,它们可能都会获得相同的当前值
    i
    ,然后递增并将其设置为
    i+1
    ,因此您将得到一次递增而不是两次递增

    例如:

    int i = 5;
    Thread 1 : i++;
               // reads value 5
    Thread 2 : i++;
               // reads value 5
    Thread 1 : // increments i to 6
    Thread 2 : // increments i to 6
               // i == 6 instead of 7
    
    为什么i++在Java中不是原子的

    让我们将增量操作分解为多个语句:

    线程1和2:

  • 从内存中获取总值
  • 将1添加到值中
  • 写回记忆
  • 如果没有同步,那么假设线程一已经读取了值3并将其增加到4,但没有将其写回。此时,将发生上下文切换。线程2读取值3,将其递增,然后发生上下文切换。虽然两个线程都增加了总值,但它仍然是4-争用状态。

    并发(线程类等)是的v1.0中增加的一个特性
    i++
    在这之前是在beta版中添加的,因此它仍然很可能是(或多或少)在其原始实现中添加的

    由程序员来同步变量。退房

    编辑:澄清一下,i++是一个定义良好的过程,早于Java,因此Java的设计者决定保留该过程的原始功能

    ++操作符是在B(1969)中定义的,它比java和线程技术早了一点。

    java规范 重要的是JVM的各种实现可能实现或可能没有实现语言的某个特性,而不是如何实现

    JLS在第15.14.2条中定义了++后缀运算符,其中表示i.a.“将值1添加到变量的值中,并将总和存储回变量中”。它没有提到或暗示多线程或原子性


    对于多线程或原子性,JLS提供了volatile和synchronized。此外,还有类。

    i++
    在Java中可能不是原子性的,因为原子性是一种特殊要求,在大多数
    i++
    的使用中并不存在。这一需求有很大的开销:使增量操作原子化的成本很高;它涉及到软件和硬件级别的同步,而这些同步不需要在普通增量中出现

    您可以提出这样的论点,即
    i++
    应该被设计并记录为专门执行原子增量,以便使用
    i=i+1
    执行非原子增量。但是,这会破坏java和C++之间的“文化兼容性”。此外,它还将取消熟悉类似C语言的程序员认为理所当然的一种方便的表示法,赋予它一种仅在有限情况下适用的特殊含义

    <>基本C或C++代码,如<代码>(i=0;i<限制;i++)<代码>将转化为java <代码>(i=0;i<限制;i=i+1)< /> >;因为使用原子
    i++
    是不合适的。更糟糕的是,从C或其他类似C的语言到Java的程序员无论如何都会使用
    i++
    ,导致不必要地使用原子指令

    即使在机器指令集级别,出于性能原因,增量类型的操作通常也不是原子操作。在x86中,必须使用特殊指令“lock prefix”使
    inc
    指令原子化:原因与上述相同。如果
    inc
    始终是原子的,则当需要非原子inc时,将永远不会使用它;程序员和编译器会生成加载、添加1和存储的代码,因为这样会更快


    在一些指令集架构中,没有原子的
    inc
    ,或者根本没有
    inc
    ;要在MIPS上执行原子inc,必须编写一个软件循环,该循环使用
    ll
    sc
    :加载链接并存储条件。Load linked读取单词,如果单词没有更改,或者它失败(检测到并导致重试),则store conditional存储新值。

    如果操作
    i++
    是原子操作,则您将没有机会从中读取值。这正是您希望使用
    i++
    (而不是使用
    ++i
    )执行的操作

    例如,请查看以下代码:

    public static void main(final String[] args) {
        int i = 0;
        System.out.println(i++);
    }
    
    在这种情况下,我们希望输出为:
    0
    (因为我们发布增量,例如先读取,然后更新)

    这是操作不能是原子操作的原因之一,因为您需要读取值(并对其进行处理),然后更新值


    另一个重要原因是,由于锁定,以原子方式执行某些操作通常需要更多的时间。在人们希望进行原子操作的罕见情况下,让原语上的所有操作花费更长的时间是愚蠢的。这就是为什么他们在语言中添加了和原子类。

    i++
    是一个只涉及3个操作的语句:

  • 读取当前值
  • 写新值
  • 存储东北
    Thread A fetches i
    Thread B fetches i
    
    Thread A overwrites i with a new value say -foo-
    Thread B overwrites i with a new value say -bar-
    Thread B stores -bar- in i
    
    // At this time thread B seems to be more 'active'. Not only does it overwrite 
    // its local copy of i but also makes it in time to store -bar- back to 
    // 'main' memory (i)
    
    Thread A attempts to store -foo- in memory effectively overwriting the -bar- 
    value (in i) which was just stored by thread B in Time 2.
    
    Thread B has nothing to do here. Its work was done by Time 2. However it was 
    all for nothing as -bar- was eventually overwritten by another thread.
    
    int temp = i;     // 1. read
    i = temp + 1;    // 2. increment the value then 3. write it back