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的你好!”)代码>
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:
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