简体   繁体   English

为什么这个多线程程序输出100而不是预期的200?

[英]Why does this multithreaded program output 100 instead of the expected 200?

I'm learning multithreading. 我正在学习多线程。 Can anyone tell why here the output is always 100, even though there are two threads which are doing 100 increments? 任何人都可以告诉为什么输出总是100,即使有两个线程正在做100个增量?

public class App {    
    public static int counter = 0;

    public static void process() {    
        Thread thread1 = new Thread(new Runnable() {    
            @Override
            public void run() {
                for (int i = 0; i < 100; ++i) {
                    ++counter;
                }    
            }
        });

        Thread thread2 = new Thread(new Runnable() {    
            @Override
            public void run() {
                for (int i = 0; i < 100; ++i) {
                    ++counter;
                }    
            }
        });

        thread1.start();
        thread2.start();    
    }

    public static void main(String[] args) {    
        process();
        System.out.println(counter);
    }
}

The output is 100. 输出为100。

You're only starting the threads, not waiting for them to complete before you print the result. 您只是启动线程,而不是在打印结果之前等待它们完成。 When I run your code, the output is 0, not 100. 当我运行您的代码时,输​​出为0,而不是100。

You can wait for the threads with 你可以等待线程

thread1.join();
thread2.join();

(at the end of the process() method). (在process()方法的最后)。 When I add those, I get 200 as output. 当我添加它们时,我得到200作为输出。 (Note that Thread.join() throws an InterruptedException , so you have to catch or declare this exception.) (请注意, Thread.join()会抛出InterruptedException ,因此您必须捕获或声明此异常。)

But I'm 'lucky' to get 200 as output, since the actual behaviour is undefined as Stephen C notes. 但我很幸运得到200作为输出,因为实际行为是未定义的,正如Stephen C所说。 The reason why is one of the main pitfalls of multithreading: your code is not thread safe . 原因之一是多线程的主要缺陷之一:您的代码不是线程安全的

Basically: ++counter is shorthand for 基本上: ++counter是简写

  1. read the value of counter 读取counter的值
  2. add 1 加1
  3. write the value of counter 写下counter的值

If thread B does step 1 while thread A hasn't finished step 3 yet, it will try to write the same result as thread A, so you'll miss an increment. 如果线程B执行步骤1而线程A尚未完成步骤3,它将尝试写入与线程A相同的结果,因此您将错过增量。

One of the ways to solve this is using AtomicInteger , eg 解决此问题的方法之一是使用AtomicInteger ,例如

public static AtomicInteger counter = new AtomicInteger(0);

...

    Thread thread1 = new Thread(new Runnable() {
        @Override
        public void run() {
            for (int i = 0; i < 100; ++i) {
                counter.incrementAndGet();
            }
        }
    });

Can anyone tell why here the output is always 100, even though there are two threads which are doing 100 increments? 任何人都可以告诉为什么输出总是100,即使有两个线程正在做100个增量?

The reason is that you have two threads writing a shared variable and a third reading, all without any synchronization. 原因是您有两个线程编写共享变量和第三个读取,所有这些都没有任何同步。 According to the Java Memory Model, this means that the actual behavior of your example is unspecified. 根据Java内存模型,这意味着您的示例的实际行为未指定。

In reality, your main thread is (probably) printing the output before the second thread starts. 实际上,您的main线程(可能)在第二个线程启动之前打印输出。 (And apparently on some platforms, it prints it before the first one starts. Or maybe, it is seeing a stale value for counter . It is a bit hard to tell. But this is all within the meaning of unspecified ) (显然在某些平台上,它会在第一个平台开始之前打印出来。或者,它可能会看到一个陈旧的counter价值。这有点难以辨别。但这都是未指定的含义)

Apparently, adding join calls before printing the results appears to fix the problem, but I think that is really by luck 1 . 显然,在打印结果之前添加join调用似乎可以解决问题,但我认为这真的是运气1 If you changed 100 to a large enough number, I suspect that you would find that incorrect counter values would be printed once again. 如果您将100更改为足够大的数字,我怀疑您会发现将再次打印不正确的counter值。

Another answer suggests using volatile . 另一个答案建议使用volatile This isn't a solution. 这不是解决方案。 While a read operation following a write operation on a volatile is guaranteed to give the latest value written, that value may be a value written by another thread. 虽然对volatile上的写操作之后的读操作保证写入最新值,但该值可以是由另一个线写入的值。 In fact the counter++ expression is an atomic read followed by an atomic write ... but the sequence is not always atomic. 实际上, counter++表达式是原子读取,后跟原子写入...但序列并不总是原子的。 If two or more threads do this simultaneously on the same variable, they are liable to lose increments. 如果两个或多个线程同时对同一个变量执行此操作,则它们可能会丢失增量。

The correct solutions to this are to either using an AtomicInteger , or to perform the counter++ operations inside a synchronized block; 对此的正确解决方案是使用AtomicInteger ,或在synchronized块内执行counter++操作; eg 例如

    for (int i = 0; i < 100; ++i) {
        synchronized(App.class) {
            ++counter;
        }
    }

Then it makes no difference that the two threads may or may not be executed in parallel. 然后,两个线程可以并行执行也可以没有区别。


1 - What I think happens is that the first thread finishes before the second thread starts. 1 - 我认为发生的是第一个线程在第二个线程开始之前完成。 Starting a new thread takes a significant length of time. 启动新线程需要相当长的时间。

In Your case, There are three threads are going to execute: one main, thread1 and thread2. 在您的情况下,将执行三个线程:一个主线程,thread1和thread2。 All these three threads are not synchronised and in this case Poor counter variable behaviour will not be specific and particular. 所有这三个线程都不同步,在这种情况下,不良的计数器变量行为将不具体和特殊。 These kind of Problem called as Race Conditions . 这类问题称为种族条件

Case1: If i add only one simple print statement before counter print like: Case1:如果我在计数器打印前只添加一个简单的打印语句,如:

process();
    System.out.println("counter value:");
    System.out.println(counter);

in this situation scenario will be different. 在这种情况下情况会有所不同。 and there are lot more.. So in these type of cases, according to your requirement modification will happen. 还有很多..所以在这些类型的情况下,根据你的要求,会发生修改。

  1. If you want to execute one thread at time go for Thread join like: 如果你想在一次执行一个线程,请转到Thread join,如:

    thread1.join(); thread1.join();

    thread2.join(); thread2.join();

join() is a Thread class method and non static method so it will always apply on thread object so apply join after thread start. join()是一个Thread类方法和非静态方法,因此它将始终应用于线程对象,因此在线程启动后应用join。

If you want to read about Multi threading in java please follow; 如果你想阅读java中的多线程请关注; https://docs.oracle.com/cd/E19455-01/806-5257/6je9h032e/index.html https://docs.oracle.com/cd/E19455-01/806-5257/6je9h032e/index.html

You are checking the result before threads are done. 您在完成线程之前检查结果。

thread1.start();
thread2.start();


try{
thread1.join();
thread2.join();
}
catch(InterruptedException e){}

And make counter variable volatile . 并使counter变量volatile

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM