1

我正在尝试重现线程干扰场景.. 但有些不对劲。请帮我理解什么

主要的

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;
    }
}

线程 1

public class T1 extends Thread {

    Counter c;

    T1(Counter c) {
        this.c = c;
    }

    public void start() {
        c.decrement(); // <-- Decrement
    }
}

线程 2

public class T2 extends Thread {

    Counter c;

    T2(Counter c) {
        this.c = c;
    }

    public void start() {
        c.increment();  // <-- Increment
    }
}

当我开始 1000000 个线程时,每个线程都在代码的非同步部分上运行,我希望一些操作会重叠。

当两个在不同线程中运行但作用于相同数据的操作交错时,就会发生干扰。这意味着这两个操作由多个步骤组成,并且步骤的顺序重叠。

Counter 实例上的操作似乎不可能交错,因为 c 上的两个操作都是单个简单的语句。但是,即使是简单的语句也可以由虚拟机转换为多个步骤。我们不会检查虚拟机采取的具体步骤——知道单个表达式 c++ 可以分解为三个步骤就足够了:

检索 c 的当前值。将检索到的值增加 1。将增加的值存储回 c。

请问我错过了什么?

4

2 回答 2

4

就目前而言,您的程序实际上并没有同时做任何事情,一切都在主线程中完成。由于您覆盖了 start 方法并且从不调用超类版本,因此您将无法获得使新线程运行的功能。

但是仅仅调用 super.start() 并不能解决问题。考虑 start 是由当前线程运行的,所以即使你设法启动了线程,增量/减量也将由调用 start() 的线程执行,并且不会进行并发访问和修改。

我会将增量/减量的东西移出 start 方法(删除它的覆盖版本)并覆盖 run 方法,而不是把它放在那里。当然,下次考虑用 Runnable 代替 Thread,用 Runnable 就更难出这种错误了。

于 2012-07-08T01:53:21.063 回答
3

你的程序有很多问题,首先是你没有启动任何线程。对于线程,线程的主体必须在 run() 方法中。Thread 类中的 start() 方法不能被覆盖。

那么你创建的线程数量就会有很大的问题:2000000。这意味着运行它们需要 2000000 个堆栈。默认堆栈大小(除非您使用 -Xss 选项指定它)在 512Kb 和 2Mb 之间。即使您使用 128Kb 的低堆栈大小,您也需要 256GB 的内存来创建这 2000000 个线程。好吧,如果您的线程不是那么短暂,您将需要那么多内存。他们只做一个工作指令然后停止,所以很可能,他们中的大多数会在你启动太多之前终止。否则您的进程将因内存不足或资源异常而崩溃。最后,大部分时间将在初始化和终止线程。我估计只有不到 1% 的线程使用的实际 CPU 用于执行计数器的实际递增或递减。

我认为如果你真的想看到重叠,你想创建更少的线程(IMO 16 或更少。无论如何,只有这么多可以同时运行,即你机器上的处理器核心数量),但这些线程应该做增量/ 循环递减。

所以有两种类型的线程,一种在计数器上增加 200000,另一种减少 200000。启动每种类型的 8 个线程。使用 Thread.join() 等待所有 16 个线程结束,然后写入计数器的结果。

于 2012-07-08T03:04:51.543 回答