线程不安全的递减/递增 - 为什么大多是正数?
我想知道java线程中不安全的递减/递增的结果,所以有我的程序:
主类:
public class Start {
public static void main(String[] args) {
int count = 10000000, pos = 0, neg = 0, zero = 0;
for (int x=0; x<10000; x++) {
Magic.counter = 0;
Thread dec = new Thread(new Magic(false, count));
Thread inc = new Thread(new Magic(true, count));
dec.start();
inc.start();
try {
inc.join();
dec.join();
} catch (InterruptedException e) {
System.out.println("Error");
}
if (Magic.counter == 0)
zero++;
else if (Magic.counter > 0)
pos++;
else
neg++;
}
System.out.println(Integer.toString(neg) + "\t\t\t" + Integer.toString(pos) + "\t\t\t" + Integer.toString(zero));
}
}
线程类:
public class Magic implements Runnable {
public static int counter = 0;
private boolean inc;
private int countTo;
public Magic(boolean inc, int countTo) {
this.inc = inc;
this.countTo = countTo;
}
@Override
public void run() {
for (int i=0;i<this.countTo;i++) {
if (this.inc)
Magic.counter++;
else
Magic.counter--;
}
}
}
我已经运行了几次程序,并且总是得到比负数更多的正结果。我还尝试更改线程启动的顺序,但这没有改变。一些结果:
Number of results < 0 | Number of results > 0 | Number of results = 0
1103 8893 4
3159 6838 3
2639 7359 2
3240 6755 5
3264 6728 8
2883 7112 5
2973 7021 6
3123 6873 4
2882 7113 5
3098 6896 6
I'm wondering about result of unsafe decrementing/incrementing in java threads, so there is my program:
Main class:
public class Start {
public static void main(String[] args) {
int count = 10000000, pos = 0, neg = 0, zero = 0;
for (int x=0; x<10000; x++) {
Magic.counter = 0;
Thread dec = new Thread(new Magic(false, count));
Thread inc = new Thread(new Magic(true, count));
dec.start();
inc.start();
try {
inc.join();
dec.join();
} catch (InterruptedException e) {
System.out.println("Error");
}
if (Magic.counter == 0)
zero++;
else if (Magic.counter > 0)
pos++;
else
neg++;
}
System.out.println(Integer.toString(neg) + "\t\t\t" + Integer.toString(pos) + "\t\t\t" + Integer.toString(zero));
}
}
Threads class:
public class Magic implements Runnable {
public static int counter = 0;
private boolean inc;
private int countTo;
public Magic(boolean inc, int countTo) {
this.inc = inc;
this.countTo = countTo;
}
@Override
public void run() {
for (int i=0;i<this.countTo;i++) {
if (this.inc)
Magic.counter++;
else
Magic.counter--;
}
}
}
I have run program few times, and always getting much more positive result then negative. I have also tried to change order of which threads starts but this changed nothing. Some results:
Number of results < 0 | Number of results > 0 | Number of results = 0
1103 8893 4
3159 6838 3
2639 7359 2
3240 6755 5
3264 6728 8
2883 7112 5
2973 7021 6
3123 6873 4
2882 7113 5
3098 6896 6
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
我敢打赌,您会通过以下更改看到完全相反的行为(即,反转分支而不更改其他任何内容):
如果为 true,这可能表明这表明了线程交互的什么?
现在,为了好玩,让
Magic.counter
变得易失性——结果[如何]改变?删除
易失性
并用锁
包围if/else
怎么样? (锁
确保完整的内存围栏并建立关键区域。它应该总是产生完美的结果。)快乐的编码。
需要考虑的事项:
Magic.counter
变量的可见性存在很多的偏差。 (我相信符合标准的 JVM 实际上可能会产生更糟糕的结果......)++
和--
运算符本质上是非原子的。I bet you will see the exact opposite behavior with the following change (that is, reverse the branches without changing anything else):
If true, what might this indicate about this indicate about the thread interactions?
Now, for fun, make
Magic.counter
volatile -- [how] do the results change?What about removing
volatile
and surrounding theif/else
with alock
? (Alock
ensures a full memory-fence and establishes a critical region. It should always yield perfect results.)Happy coding.
Things to consider:
Magic.counter
variable. (I believe a conforming JVM could actually yield far worse results...)++
and--
operators are inherently non-atomic.一般来说,这是由于 Java 内存模型的工作方式造成的。您正在两个不同的线程中访问共享变量,而无需同步。变量既没有被声明为易失性,也没有运行原子操作。
缺乏协调和原子或易失性变量将导致 JVM 在执行时对线程代码进行内部优化。此外,未跨越内存屏障(即同步)的非易失性变量将导致每个线程缓存值,从而在其缓存内产生两个冲突的线程本地副本。
鉴于 Java 中缺乏顺序一致性模型、复杂的运行时优化以及所使用的 JVM 和底层系统(单核或多核、超线程)的特殊性,不可能确定性地预测结果 - 只是因为它违反了多个-Java 语言模型的线程约定。在同一台机器上运行完全相同的代码可能仍然会产生类似的结果,但由于线程调度、其他操作系统进程的 CPU 使用情况等的影响,它们不太可能完全相同。
以下是有关 JMM 的一些资源:http://www.cs.umd.edu /~pugh/java/memoryModel/
Generally speaking, this is due to the way the Java memory model works. You are accessing shared variables in two distinct threads without synchronization. Neither is the variable declared volatile, nor are you running atomar operations.
The absence of coordination and atomar or volatile variables will lead to internal optimizations of the threaded code done by the JVM when executing. Also, non-volatile variables that have not crossed the memory barrier (i.e.
synchronized
) will lead to cached values per thread – and thus two conflicting thread-local copies inside their caches.Given the absence of a sequential consistency model in Java, the complex runtime optimizations and the peculiarities of the used JVM and underlying system(single or multi-core, hyperthreading), it's impossible to predict the result deterministically – just because it is violating several multi-threading conventions for the Java language model. Running the exact same code on the same machine might still lead to similar results, but due to effects of thread scheduling, CPU usage of other OS processes etc. they will not likely be exactly the same.
Here are some resources about the JMM: http://www.cs.umd.edu/~pugh/java/memoryModel/