我正在尝试重现线程干扰场景..但有些事情是不对的。请帮我理解一下
public static void main(String args[]) throws InterruptedException {
Counter c = new Counter();
for (int i = 0; i < 1000000; i++) {
new T1(c).start();
new T2(c).start();
}
System.out.println(c.value()); // <-- Expect this to sometimes not be 0
}
class Counter {
private int c = 0;
public void increment() { // <-- intentionally not synchronized
c++;
}
public void decrement() { // <-- intentionally not synchronized
c--;
}
public int value() {
return c;
}
}
public class T1 extends Thread {
Counter c;
T1(Counter c) {
this.c = c;
}
public void start() {
c.decrement(); // <-- Decrement
}
}
public class T2 extends Thread {
Counter c;
T2(Counter c) {
this.c = c;
}
public void start() {
c.increment(); // <-- Increment
}
}
当我启动1000000个线程时,每个线程都运行在非同步代码段上,我希望某些操作重叠。
两个操作发生干扰,运行不同 线程,但作用于相同的数据,交错。这意味着 两个操作由多个步骤和步骤序列组成 重叠。
对Counter实例的操作似乎不太可能 interleave,因为对c的两个操作都是单个简单的语句。 但是,即使是简单的语句也可以转换为多个步骤 虚拟机。我们不会检查虚拟的具体步骤 机器需要 - 知道单个表达式c ++就足够了 可以分解为三个步骤:
检索c的当前值。将检索的值增加1。 将增加的值存储回c。
我缺少什么?
答案 0 :(得分:4)
由于你的程序实际上并没有同时执行任何操作,所以一切都在主线程中完成。由于您重写了start方法并且从不调用超类版本,因此您无法获得使新线程运行的功能。
但是只是调用super.start()不会解决问题。考虑start是由当前线程运行的,所以即使你设法启动线程,增量/减量也将由调用start()的线程执行,并且没有并发访问和修改。
我将递增/递减的东西移出start方法(删除它的重写版本)并覆盖run方法,而不是将其放在那里。当然,考虑下次使用Runnable而不是Thread,使用Runnable会更难产生这种错误。
答案 1 :(得分:2)
您的程序存在很多问题,首先是您没有启动任何线程。对于线程,线程的主体必须在run()方法中。不能覆盖Thread类中的start()方法。
然后,您将创建的线程数量存在很大问题:2000000。这意味着2000000个堆栈要运行它们。默认堆栈大小(除非您使用-Xss选项指定它)介于512Kb和2Mb之间。即使你使用128Kb的低堆栈大小,你需要256GB的内存来创建这2000000个线程。好吧,如果你的线程不那么短暂,你需要那么多的内存。他们只做一个工作指令然后停止,所以可能大多数工作指令会在你开始太多工作之前终止。或者您的进程将因内存或资源不足而崩溃。最后,大部分时间都是在初始化和终止线程。我估计不到1%的线程使用的实际CPU用于计数器的实际递增或递减。这也意味着在操作中看到任何重叠是不太可能的。
我认为如果你真的想看到重叠,你想要创建更少的线程(IMO 16或更少。任何方式,只有这么多可以同时运行,即你的机器上的处理器核心数),但这些线程应该在循环中进行增量/减量。
所以有两种类型的线程,一种类型在计数器上增加200000,另一种减少200000次。启动每种类型的8个线程。使用Thread.join()等待所有16个线程的结束,然后写入计数器的结果。