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

时间:2019-04-26 11:42:53

标签: java multithreading

我正在学习多线程。谁能说出为什么即使有两个线程以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);
    }
}

输出为100。

4 个答案:

答案 0 :(得分:11)

您只是启动线程,而不等待它们完成后再打印结果。当我运行您的代码时,输​​出为0,而不是100。

您可以通过以下方式等待线程

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

(在process()方法的末尾)。当我添加这些时,我得到200作为输出。 (请注意,Thread.join()会引发InterruptedException,因此您必须捕获或声明此异常。)

但是我很幸运获得200作为输出,因为正如Stephen C指出的那样,实际行为是不确定的。之所以成为多线程的主要陷阱之一,是因为您的代码不是线程安全的

基本上:++counter是速记

  1. 读取counter的值
  2. 添加1
  3. 写出counter的值

如果线程B在线程A尚未完成步骤3的情况下执行了步骤1,则它将尝试写入与线程A相同的结果,因此您将错过增量。

解决此问题的方法之一是使用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();
            }
        }
    });

答案 1 :(得分:6)

  

有人能说出为什么即使有两个线程以100为增量进行输出,这里的输出总是100?

原因是您有两个线程在编写共享变量,而在第三个线程中却没有任何同步。根据Java内存模型,这意味着您的示例的实际行为是不确定的。

实际上,您的main线程正在(可能)在第二个线程开始之前打印输出。 (显然,在某些平台上,它会在第一个平台开始之前将其打印出来。或者也许,它看到counter的值已过时。这很难说。但这全都在未指定)

显然,在打印结果出现之前添加了join调用来解决此问题,但是我认为这确实是靠运气 1 来解决的。如果将100更改为足够大的数字,我怀疑您会发现错误的counter值将再次打印。

另一个答案建议使用volatile。这不是解决方案。尽管可以保证对volatile进行写操作之后的读操作可以提供最新写入的值,但是该值可以是另一个线程写入的值。实际上,counter++表达式是原子读取,然后是原子写入...,但是序列并不总是原子的。如果两个或多个线程同时对同一个变量执行此操作,则它们很容易丢失增量。

对此的正确解决方案是使用AtomicInteger或在counter++块内执行synchronized操作;例如

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

然后这两个线程可以并行执行也可以不并行执行没有区别。


1-我认为发生的事情是第一个线程在第二个线程开始之前完成。启动新线程会花费大量时间。

答案 2 :(得分:1)

在您的情况下,将要执行三个线程:一个主线程,线程1和线程2。所有这三个线程均未同步,因此在这种情况下,计数器变量的行为不明确也不特定。 这种问题称为Race Conditions

Case1:如果在计数器打印之前仅添加一个简单的打印语句,例如:

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

在这种情况下,情况将有所不同。还有更多.. 因此,在这种情况下,将根据您的要求进行修改。

  1. 如果您想一次执行一个线程,请像以下这样进行线程连接:

    thread1.join();

    thread2.join();

join()是一个Thread类方法,并且是非静态方法,因此它将始终应用于线程对象,因此在线程启动后应用join。

如果您想阅读有关Java中的多线程的信息,请遵循; https://docs.oracle.com/cd/E19455-01/806-5257/6je9h032e/index.html

答案 3 :(得分:0)

您正在检查线程完成之前的结果。

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


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

并设置counter变量volatile