And*_*302 94 java concurrency multithreading
为什么i++Java中没有原子?
为了更深入地了解Java,我试图计算线程循环执行的频率.
所以我用了一个
private static int total = 0;
Run Code Online (Sandbox Code Playgroud)
在主要班级.
我有两个主题.
System.out.println("Hello from Thread 1!");System.out.println("Hello from Thread 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();
}
}
Run Code Online (Sandbox Code Playgroud)
Kaz*_*Kaz 119
i++在Java中可能不是原子的,因为原子性是一个特殊的要求,在大多数用途中都不存在i++.该要求具有显着的开销:使增量操作成为原子的成本很高; 它涉及在普通增量中不需要存在的软件和硬件级别的同步.
您可以将i++应该设计和记录的参数设置为专门执行原子增量,以便使用执行非原子增量i = i + 1.但是,这会破坏Java,C和C++之间的"文化兼容性".同样,它会删除一个方便的符号,熟悉类C语言的程序员认为这是理所当然的,赋予它一个特殊的含义,仅适用于有限的情况.
基本的C或C++代码就像for (i = 0; i < LIMIT; i++)转换为Java一样for (i = 0; i < LIMIT; i = i + 1); 因为使用原子是不合适的i++.更糟糕的是,程序员从C语言或其他类C语言到Java都会使用i++,导致不必要地使用原子指令.
即使在机器指令集级别,由于性能原因,增量类型操作通常也不是原子的.在x86中,必须使用特殊指令"lock prefix"来使inc指令成为原子:出于与上述相同的原因.如果inc总是原子的,那么当需要非原子公司时,它永远不会被使用; 程序员和编译器会生成加载,添加1和存储的代码,因为它会更快.
在一些指令集架构中,没有原子inc或根本没有inc; 要在MIPS上执行原子公司,你必须编写一个使用lland sc:load-linked和store-conditional 的软件循环.加载链接读取单词,如果单词未更改,则store-conditional存储新值,否则失败(检测到并导致重新尝试).
Era*_*ran 33
i++ 涉及两个操作:
ii当两个线程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
Run Code Online (Sandbox Code Playgroud)
Jon*_*nne 11
重要的是JLS(Java语言规范),而不是JVM的各种实现如何实现或不实现该语言的某些特性.JLS在第15.14.2节中定义了++后缀运算符,它表示"值1被添加到变量的值中,并且总和被存储回变量".它没有提到或提示多线程或原子性.对于这些,JLS提供volatile和synchronized.另外,还有java.util.concurrent.atomic包(参见http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/atomic/package-summary.html)
i++ 是一个只涉及 3 个操作的语句:
这三个操作并不意味着在单个步骤中执行,或者换句话说i++不是 复合操作。因此,当多个线程参与一个单一但非复合的操作时,各种事情都可能出错。
考虑以下场景:
时间 1 :
Thread A fetches i
Thread B fetches i
Run Code Online (Sandbox Code Playgroud)
时间 2 :
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)
Run Code Online (Sandbox Code Playgroud)
时间 3 :
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.
Run Code Online (Sandbox Code Playgroud)
你有它。竞争条件。
这就是为什么i++不是原子的。如果是这样,这一切都fetch-update-store不会发生,并且每个都会原子地发生。这正是AtomicInteger它的用途,在您的情况下,它可能适合。
聚苯乙烯
一本涵盖所有这些问题的好书,然后是: Java Concurrency in Practice
为什么i ++在Java中不是原子的?
让我们将增量操作分解为多个语句:
线程1和2:
如果没有同步,那么让我们说线程1读取值3并将其增加到4,但是没有写回来.此时,发生上下文切换.线程2读取值3,递增它并发生上下文切换.虽然两个线程都增加了总值,但仍然是4 - 竞争条件.
| 归档时间: |
|
| 查看次数: |
20937 次 |
| 最近记录: |