Java 为什么这个多线程计数器会产生正确的结果?

Java 为什么这个多线程计数器会产生正确的结果?,java,multithreading,Java,Multithreading,我正在学习多线程计数器,我想知道为什么不管我运行代码多少次,它都会产生正确的结果 public class MainClass { public static void main(String[] args) { Counter counter = new Counter(); for (int i = 0; i < 3; i++) { CounterThread thread = new CounterThread(coun

我正在学习多线程计数器,我想知道为什么不管我运行代码多少次,它都会产生正确的结果

public class MainClass {
    public static void main(String[] args) {
        Counter counter = new Counter();
        for (int i = 0; i < 3; i++) {
            CounterThread thread = new CounterThread(counter);
            thread.start();
        }
    }
}

public class CounterThread extends Thread {
    private Counter counter;
    public CounterThread(Counter counter) {
        this.counter = counter;
    }
    public void run() {
        for (int i = 0; i < 10; i++) {
            this.counter.add();
        }
        this.counter.print();
    }
}

public class Counter {
    private int count = 0;
    public void add() {
        this.count = this.count + 1;
    }
    public void print() {
        System.out.println(this.count);
    }
}
不确定这是侥幸还是意料之中?我以为结果会是

10
10
10

尝试将循环计数从10增加到10000,您可能会在输出中看到一些差异

最合乎逻辑的解释是,只有10次添加,一个线程的速度太快,无法在下一个线程开始并添加到上一个结果之上之前完成

我正在学习多线程计数器,我想知道为什么不管我运行代码多少次,它都会产生正确的结果

public class MainClass {
    public static void main(String[] args) {
        Counter counter = new Counter();
        for (int i = 0; i < 3; i++) {
            CounterThread thread = new CounterThread(counter);
            thread.start();
        }
    }
}

public class CounterThread extends Thread {
    private Counter counter;
    public CounterThread(Counter counter) {
        this.counter = counter;
    }
    public void run() {
        for (int i = 0; i < 10; i++) {
            this.counter.add();
        }
        this.counter.print();
    }
}

public class Counter {
    private int count = 0;
    public void add() {
        this.count = this.count + 1;
    }
    public void print() {
        System.out.println(this.count);
    }
}
看看@manouti的答案

即使您共享的是同一个未同步的
计数器
对象,但仍有一些原因导致您的3个线程通过数据同步连续运行(或看起来它们正在运行)。我必须在我的8进程Intel Linux机器上努力工作,才能让它显示任何交错

  • 当线程开始和结束时,内存障碍会被跨越。根据Java内存模型,保证执行
    thread.join()
    的线程将看到发布到它的线程的结果,但我怀疑当线程完成时会发生中央内存刷新。这意味着,如果线程连续运行(对于这样一个小循环,它们很难不运行),它们的行为就好像没有并发一样,因为它们将看到彼此对
    计数器的更改

  • 放置
    线程。睡眠(100)run()
    方法前面的code>会导致它不连续运行。它还希望使线程缓存
    计数器
    ,而看不到其他已完成的线程发布的结果。但我仍然需要帮助

  • 在所有线程都被实例化后启动循环中的线程有助于提高并发性

  • 导致同步的另一个原因是:

    System.out.println(this.count);
    
    System.out
    是一个
    Printstream
    同步类。每次线程调用
    println(…)
    时,它都将其结果发布到中央内存。如果您记录该值,然后稍后再显示,则可能会显示更好的交错

  • 我真的很想知道,在某个点上,
    计数器
    类是否导致了部分人工同步。例如,我真的很惊讶
    Thread.run()
    方法前面和末尾的
    Thread.sleep(1000)
    没有显示10,10,10

  • 应该注意的是,在非英特尔体系结构上,使用不同的内存和/或线程型号,这可能更容易复制

  • 哦,作为注释和空谈,通常建议实现
    Runnable
    ,而不是扩展
    Thread

下面是我对你的测试程序的调整

public class CounterThread extends Thread {
    private Counter counter;
    int result;
    ...
    public void run() {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e1) {
            Thread.currentThread().interrupt();  // good pattern
            return;
        }
        for (int i = 0; i < 10; i++) {
            counter.add();
        }
        result = counter.count;
        // no print here
    }
}
公共类计数器线程扩展线程{
私人柜台;
int结果;
...
公开募捐{
试一试{
睡眠(100);
}捕捉(中断异常e1){
Thread.currentThread().interrupt();//模式良好
返回;
}
对于(int i=0;i<10;i++){
counter.add();
}
结果=计数器计数;
//这里没有印刷品
}
}
然后,您的主服务器可以执行以下操作:

Counter counter = new Counter();
List<CounterThread> counterThreads = new ArrayList<>();
for (int i = 0; i < 3; i++) {
    counterThread.add(new CounterThread(counter));
}
// start in a loop after constructing them all which improves the overlap chances
for (CounterThread counterThread : counterThreads) {
    counterThread.start();
}
// wait for them to finish
for (CounterThread counterThread : counterThreads) {
    counterThread.join();
}
// print the results
for (CounterThread counterThread : counterThreads) {
    System.out.println(counterThread.result);
}
计数器计数器=新计数器();
List counterThreads=new ArrayList();
对于(int i=0;i<3;i++){
反螺纹。添加(新反螺纹(计数器));
}
//在构建它们之后开始一个循环,这提高了重叠的机会
用于(反螺纹反螺纹:反螺纹){
counterThread.start();
}
//等待他们完成
用于(反螺纹反螺纹:反螺纹){
反螺纹连接();
}
//打印结果
用于(反螺纹反螺纹:反螺纹){
System.out.println(counterThread.result);
}
即使这样,我从来没有在我的盒子上看到10,10,10输出,我经常看到10,20,30输出。最接近的是12,12,12


向您展示正确测试线程化程序有多困难。相信我,如果这段代码在生产中,而您希望“免费”同步,那么它将使您失败

试着数到一百万。或者一千万。我认为你需要尝试更大的价值。可能是第一个线程在第二个线程完全初始化并准备就绪之前就已经完成了。谢谢。就这样!。这个故事的寓意是——即使您的代码在每次尝试时都能正常工作,但这并不意味着您没有种族条件。仅通过运行代码无法可靠地检测竞争条件。