我正在学习多线程。谁能说出为什么即使有两个线程以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。
答案 0 :(得分:11)
您只是启动线程,而不等待它们完成后再打印结果。当我运行您的代码时,输出为0,而不是100。
您可以通过以下方式等待线程
thread1.join();
thread2.join();
(在process()
方法的末尾)。当我添加这些时,我得到200作为输出。 (请注意,Thread.join()
会引发InterruptedException
,因此您必须捕获或声明此异常。)
但是我很幸运获得200作为输出,因为正如Stephen C指出的那样,实际行为是不确定的。之所以成为多线程的主要陷阱之一,是因为您的代码不是线程安全的。
基本上:++counter
是速记
counter
的值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);
在这种情况下,情况将有所不同。还有更多.. 因此,在这种情况下,将根据您的要求进行修改。
如果您想一次执行一个线程,请像以下这样进行线程连接:
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
。