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
。在下面的代码中,我们任意选择名为 a1
的 AtomicInteger
作为 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
对象提交到后台线程上 运行。
有代码:
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
。在下面的代码中,我们任意选择名为 a1
的 AtomicInteger
作为 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
对象提交到后台线程上 运行。