原子整数 a1 在 a2 之前增加,在 a2 之后减少,为什么存在 a2 > a1



有一个代码:

AtomicInteger a1 = new AtomicInteger();
AtomicInteger a2 = new AtomicInteger();
for (int i = 0; i < 100; i++) {
new Thread(()->{
for (int j = 0; j < 1e4; j++) {
a1.incrementAndGet();
a2.incrementAndGet();
int v2 = a2.decrementAndGet();
if(v2>a1.get()){
System.out.println("error a2 > a1");
}
a1.decrementAndGet();
}
}).start();
}

为什么存在printlnerror a2 > a1

谢谢!

操作a1a2
a1+10
a2+11
a2-10
a1-00

想象一下这个场景

所有100个线程都完成了增量,最后一个线程现在具有a1 = 100, a2 = 100

现在,第一个计算v2的线程将获得v2 = 99

如果其他99个线程在一个线程继续执行之前完成并递减,那么它将检查99 > 1并为true。

简短回答:其他线程可以在int v2 = a2.decrementAndGet()if(v2>a1.get())之间递减a1

jdkramoft的答案是正确的。您有两个资源(一对AtomicInteger对象(在不受保护的线程之间进行操作。在每个AtomicInteger上,每个单独的调用递增、递减和获取都是原子性的。但是跨线程的多个调用可能是交错的,不是原子的,也不是线程安全的。

要使增量、减量和get调用组成为原子调用,必须将它们作为一个组进行保护。一个简单的方法是使用synchronized。在下面的代码中,我们任意选择了名为a1AtomicInteger作为synchronized调用的锁定对象。

通过同步同一对象(a1(上的代码块,我们保证一次只能有一个线程运行该代码块。当另一个线程到达试图运行该块的点时,它必须等待synchronized锁被释放。

1e4的使用有点珍贵,所以让我们使用10_000。即使是1_000也足以满足我们的目的,而且不太可能破坏我们的控制台缓冲区。

我不是并发方面的专家,但以下代码对我来说似乎是线程安全的。也许其他人可以指出任何缺陷。无论如何,使用风险自负。

package work.basil.demo.threads;
import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.atomic.AtomicInteger;
public class App
{
public static void main ( String[] args )
{
System.out.println( "INFO - Starting the main method of our demo. " + Instant.now() );
AtomicInteger a1 = new AtomicInteger();
AtomicInteger a2 = new AtomicInteger();
for ( int i = 0 ; i < 100 ; i++ )
{
System.out.println( "Instantiating thread # " + i + "  |  " + Instant.now() );
new Thread( ( ) -> {
for ( int j = 0 ; j < 1_000 ; j++ )
{
synchronized ( a1 )
{
a1.incrementAndGet();
a2.incrementAndGet();
int v2 = a2.decrementAndGet();
if ( v2 > a1.get() )
{
System.out.println( "error a2 > a1" );
}
a1.decrementAndGet();
}
System.out.println( "Finishing thread id " + Thread.currentThread().getId() + " | " + Instant.now() );
}
} ).start();
}
try { Thread.sleep( Duration.ofSeconds( 10 ).toMillis() ); } catch ( InterruptedException e ) { e.printStackTrace(); }
System.out.println( "a1 = " + a1.get() );
System.out.println( "a2 = " + a2.get() );
}
}

运行时。

INFO - Starting the main method of our demo. 2021-06-06T00:05:12.869874Z
Instantiating thread # 0  |  2021-06-06T00:05:12.875289Z
Instantiating thread # 1  |  2021-06-06T00:05:12.887569Z
Instantiating thread # 2  |  2021-06-06T00:05:12.888805Z
Instantiating thread # 3  |  2021-06-06T00:05:12.891298Z
Finishing thread id 16 | 2021-06-06T00:05:12.891789Z
Finishing thread id 16 | 2021-06-06T00:05:12.894783Z
…
Finishing thread id 57 | 2021-06-06T00:05:13.634241Z
Finishing thread id 57 | 2021-06-06T00:05:13.634245Z
Finishing thread id 57 | 2021-06-06T00:05:13.634247Z
a1 = 0
a2 = 0

顺便提一下:在现代Java中,我们很少需要直接寻址Thread类。最好使用添加到Java5中的Executors框架。将您的任务作为Runnable/Callable对象提交,以便在后台线程上运行。

相关内容

最新更新