AtomicInteger a1在a2之前增加,在a2之后减少,为什么存在a2 > a1

AtomicInteger a1 increase before a2, and decrease after a2, why exists 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();
        }

为什么存在 println error a2 > a1?

谢谢!

动作 a1 a2
a1+ 1 0
a2+ 1 1
a2- 1 0
a1- 0 0

使用redhat windows openjdk-1.8.0.222

想象一下这个场景:

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

现在第一个计算v2的线程会得到v2 = 99.

如果其他 99 个线程在该线程继续运行之前完成并递减,则它将检查 99 > 1 并为真。

简答:其他线程可以在int v2 = a2.decrementAndGet()if(v2>a1.get())

之间递减a1

是正确的。您有两个资源(一对 AtomicInteger 对象)正在跨不受保护的线程进行操作。每个 个人 对每个 AtomicInteger 递增、递减和获取的调用都是自动发生的。但是跨线程的多次调用可能是交错的,不是原子的,不是线程安全的。

要使递增、递减和获取调用组原子化,您必须将它们作为一个组来保护。一种简单的方法是使用 synchronized。在下面的代码中,我们任意选择名为 a1AtomicInteger 作为 synchronized 调用的锁定对象。

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

使用1e4有点珍贵,还是用10_000代替吧。即使是 1_000 也足以满足我们的目的,而且不太可能 blow out our console buffer.

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

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 class。最好使用添加到 Java 的执行器框架 5. 将您的任务作为 Runnable/Callable 对象提交到后台线程上 运行。