java线程以串行而非并行方式运行
我试图学习Java中的并发性,但无论我做什么,2个线程都是串行运行的,而不是并行运行的,因此我无法复制教程中解释的常见并发性问题(如线程干扰和内存一致性错误)。示例代码:
public class Synchronization {
static int v;
public static void main(String[] args) {
Runnable r0 = () -> {
for (int i = 0; i < 10; i++) {
Synchronization.v++;
System.out.println(v);
}
};
Runnable r1 = () -> {
for (int i = 0; i < 10; i++) {
Synchronization.v--;
System.out.println(v);
}
};
Thread t0 = new Thread(r0);
Thread t1 = new Thread(r1);
t0.start();
t1.start();
}
}
这总是给我一个从1开始到0结束的结果(无论循环长度是多少)。例如,上面的代码每次都会给我:
一, 2. 3. 4. 5. 6. 7. 8. 9 10 9 8. 7. 6. 5. 4. 3. 2. 1. 0
有时,第二个线程先启动,结果相同,但为负,因此它仍以串行方式运行
在Intellij和Eclipse中都进行了尝试,结果相同。如果重要的话,CPU有两个核心
更新:它最终变得可以复制,具有巨大的循环(从1_000_000开始),尽管仍然不是每次都可以复制,只是最终的差异很小。也似乎使循环中的操作“更重”,比如打印线程名称也使其更具可复制性。手动将睡眠添加到线程中也能起作用,但可以这么说,这会让实验变得不那么干净。原因似乎不是第一个循环在第二个循环开始之前完成,因为我看到两个循环都打印到控制台,同时继续操作,最后仍然给我0。原因似乎更像是同一变量的线程竞赛。我会更深入地研究,谢谢
# 1 楼答案
似乎第一个启动的线程从来没有机会在线程竞赛中获得第二个变量/第二个线程从来没有时间启动(不能确定),所以第二个线程几乎总是等待第一个循环完成
一些繁重的操作将混合结果:
TimeUnit.MILLISECONDS.sleep(100);
*这并不总是正确的,但你在测试中很幸运
# 2 楼答案
启动线程是一个重量级的操作,这意味着它需要一些时间来执行。由于这个事实,当你开始第二个线程时,第一个线程就完成了
为什么有时它处于“恢复顺序”的原因在于线程调度程序的工作方式。根据规范,线程执行顺序没有保证——记住这一点,我们知道第二个线程可以先运行(并完成)
将迭代次数增加到有意义的数量,比如10000,然后看看会发生什么
# 3 楼答案
根据Brian Goetz(《实践中的Java并发性》一书的作者)的说法,这被称为幸运计时。由于没有与静态变量
v
的同步,很明显这个类不是线程安全的