java中意外的多线程输出


public class ConTest {
@Test
void name2() {
final MyCounter myCounter = new MyCounter();
final Thread t1 = new Thread(() ->
myCounter.increment()
);
final Thread t2 = new Thread(() ->
myCounter.increment()
);
t1.start();
t2.start();
System.out.println(myCounter.count);
}
@Test
void name3() {
final MyCounter myCounter = new MyCounter();
final ExecutorService service = Executors.newFixedThreadPool(2);
for (int i = 0; i < 2; i++) {
service.execute(() -> {
myCounter.increment();
});
}
System.out.println(myCounter.count);
}
static class MyCounter {
private AtomicLong count = new AtomicLong();
public void increment() {
count.incrementAndGet();
}
}
}

AtomicLong在多线程时是安全的。

也就是说,在上面的例子中,它是在2个线程中执行的,所以无论执行多少次,结果都应该是2。

然而,在这两个测试都尝试了几次之后,结果有时是1。为什么会发生这种情况?

在打印计数器的值之前,您不需要等待任何后台线程或任务结束。为了等待任务退出,您需要为线程添加以下内容:

t1.join();
t2.join();

为服务添加这个,它可以防止新任务被添加,并等待一段合理的时间让它们结束:

service.shutdown();
boolean done = awaitTermination(pickSuitablyLongPeriod, TimeUnit.MILLISECONDS);

确保后台任务完成后,在运行时应该打印正确的结果:

System.out.println(myCounter.count);

这是因为当您调用System.out.println时线程仍在处理。在这种情况下,您需要在打印计数器之前阻塞主线程。

Executor的例子中,你可以等待终止:

final ExecutorService service = Executors.newFixedThreadPool(2);
final MyCounter myCounter = new MyCounter();
for (int i = 0; i < 100; i++) {
service.submit(myCounter::increment);
}
service.shutdown();
while (!service.awaitTermination(100, TimeUnit.MILLISECONDS)) {
System.out.println("waiting");
}
System.out.println(myCounter.count);

你应该避免在生产代码中阻塞,看看发布/订阅设计模式

不要忘记对executor使用shutdown()

见注释:

// Here you start the 2  threads 
for (int i = 0; i < 2; i++) {
service.execute(() -> {
myCounter.increment();
});
}
// we are not sure here that your 2 threads terminate their tasks or not !!
// the print will be executed by the Main Thread and maybe before the 2 threads terminate their 
// job ,
// maybe just one terminate , maybe no one from your 2 threads increment the count .
System.out.println(myCounter.count);

你可以使用Future类,而不是execute你可以使用submit(),返回类型将是类型Futre(accept void),之后返回Future对象,方法get()将阻止执行,直到从服务返回结果:

示例方法name3():将始终返回2

void name3() {
final MyCounter myCounter = new MyCounter();
final ExecutorService service = Executors.newFixedThreadPool(2);
Future<?> f = null;
for (int i = 0; i < 2; i++) {
f =service.submit(() -> {
myCounter.increment();
});
try {
f.get();
} catch (InterruptedException | ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(myCounter.count);
service.shutdown();
}

除了上面的答案之外,您还可以添加一些打印内容,以便更好地理解正在发生的事情。

在总结。在等待结果之前,您需要等待线程完成执行,因此这不是AtomicLong的问题。

我修改了代码,添加了一些打印,下面是执行的结果。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicLong;
import org.junit.jupiter.api.Test;
public class ConTest {
@Test
void name2() {
final MyCounter myCounter = new MyCounter();
final Thread t1 = new Thread(() -> {
myCounter.increment();
System.out.println("Counter increment t1 completed and the value is " + myCounter.getCount());
});
final Thread t2 = new Thread(() -> {
myCounter.increment();
System.out.println("Counter increment t2 completed and the value is " + myCounter.getCount());
});
t1.start();
t2.start();
System.out.println(myCounter.count.get());
}
@Test
void name3() {
final MyCounter myCounter = new MyCounter();
final ExecutorService service = Executors.newFixedThreadPool(2);
for (int i = 0; i < 2; i++) {
service.execute(() -> {
myCounter.increment();
System.out.println("incrementing for count and the value is " + myCounter.getCount());
});
}
System.out.println(myCounter.count.get());
}
class MyCounter {
private AtomicLong count = new AtomicLong();
public void increment() {
count.incrementAndGet();
}
public long getCount(){
return count.get();
}
}
}

结果(name2)

1
Counter increment t1 completed and the value is 1
Counter increment t2 completed and the value is 2

结果(name3)

incrementing for count and the value is 1
1
incrementing for count and the value is 2

您也可以使用调试器来更好地理解