AtomicLong 不能在线程之间准确传递
AtomicLong does not pass accurately between threads
该练习与 Effective Java 中的第 78 项相关。也就是说,我们创建了两个并行递增公共静态变量的线程并将其打印出来。目标是为控制台生成一条统一的递增数字行。
AtomicLong 用于避免竞争条件,但有一个我无法解释的错误。
即,第一次调用
System.out.println(i.getAndIncrement());
JVM 没有读取最新的变量值。仅在第二次调用时读取。 Please see the console output with inconsistent output marked
有人可以建议我学习什么来自己清除这个错误吗?是时候阅读 JVM 规范了吗?
package com.util.concurrency.tick;
import java.util.concurrent.atomic.AtomicLong;
public class AtomicIncrementer implements Runnable {
private String name;
private static final int MAXI = 1000;
private static final AtomicLong i = new AtomicLong(-1);
public AtomicIncrementer(String name){
this.name = name;
}
public void run(){
while(i.get() < MAXI){
System.out.println(name+ ". i = "
+i.getAndIncrement());
}
System.out.println(name+" i = "+i.get());
}
public static void main(String[] args){;
try {
Thread t1 = new Thread(new AtomicIncrementer("A"));
Thread t2 = new Thread(new AtomicIncrementer("B"));
t1.start();
t2.start();
} catch (Exception e) {
}
System.out.println("Two incrementers launched");
}
}
基本上你没用过同步.
您可以更改 run
方法,如下所示:
public void run(){
synchronized(i){
while(i.get() < MAXI){
System.out.println(name+ ". i = "
+i.getAndIncrement());
}
System.out.println(name+" i = "+i.get());
}
}
此外,您可以参考本教程以了解有关 同步 的更多信息:https://www.tutorialspoint.com/java/java_thread_synchronization.htm
这可能是您想要做的:) 请检查 java 文档。你想在 2 个线程之间同步。基本上每个线程得到 1 轮递增并将计数器传递给另一个线程。
public class AtomicIncrementer implements Runnable {
private static final int MAXI = 1000;
private static final SynchronousQueue<Long> LONG_EXCHANGER = new SynchronousQueue<>();
private final String name;
private AtomicIncrementer(String name) {
this.name = name;
}
@Override
public void run() {
try {
while (true) {
Long counter = LONG_EXCHANGER.take();
if (counter >= MAXI) {
LONG_EXCHANGER.put(counter);
break;
}
System.out.println(name + ". i = " + (counter + 1));
LONG_EXCHANGER.put(counter + 1);
}
Long counter = LONG_EXCHANGER.take();
System.out.println(name + " final i = " + counter);
LONG_EXCHANGER.put(counter);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Thread t1 = new Thread(new AtomicIncrementer("A"));
Thread t2 = new Thread(new AtomicIncrementer("B"));
t1.start();
t2.start();
System.out.println("Two incrementers launched");
try {
LONG_EXCHANGER.put(-1L);
t1.join();
System.out.println("T1 ended");
//this is needed for last thread to end
LONG_EXCHANGER.take();
t2.join();
System.out.println("T2 ended");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
同步不是问题,因为 AtomicLong 就是为了这个。
更确切地说,共享字段必须在线程切换时从 & 更新到本地线程内存。因为存在 volatile
:
private static final volatile AtomicLong i = new AtomicLong(-1);
其实我也不是很确定!对于 long
情况很清楚,
但是只有一个对象;正在更新其长字段。
这里有很多竞争条件。
以下事件顺序将导致您看到的结果:
线程 A 运行到 i=198
。然后是上下文切换,线程 B 运行以下命令:
System.out.println(name+ ". i = "
+i.getAndIncrement());
在 while 循环中。
线程 B 创建字符串:
"B. i = 198"
但是在线程 B 有机会打印该字符串之前,上下文再次切换到线程 A。
所以线程 A 继续执行,直到它打印
A. i=204
然后有一个线程 B 的上下文切换,它从之前停止的地方恢复,它正在打印字符串:
B. i = 198
基本上,在获取 i
的当前值并打印它之前,您有一个竞争条件。
也就是说i.getAndIncrement();
是一个原子操作。
但是,
System.out.println(name+ ". i = "
+i.getAndIncrement());
不是原子操作。
您这里有多项操作。在伪代码中:
1. tempInt = i.getAndIncrement();
2. tempString1 = name + ". i = ";
3. tempString2 = convertToString(tempInt);
4. tempString3 = tempString1 + tempString2;
5. print tempString3;
这就是为什么你的输出如此混乱:)
如果你想深入理解这些概念,我推荐这个在线课程:
https://www.udemy.com/java-multithreading-concurrency-performance-optimization/?couponCode=CONCURRENCY
真的很便宜,几个小时就能吃完。但它确实深入探讨了原子 类 的那些概念和注意事项。这是一个很好的时间投资。
在你的情况下你想要做的确实是使用同步而不是 AtomicLong,因为你这里有一个多操作临界区。
希望对您有所帮助。
该练习与 Effective Java 中的第 78 项相关。也就是说,我们创建了两个并行递增公共静态变量的线程并将其打印出来。目标是为控制台生成一条统一的递增数字行。 AtomicLong 用于避免竞争条件,但有一个我无法解释的错误。 即,第一次调用
System.out.println(i.getAndIncrement());
JVM 没有读取最新的变量值。仅在第二次调用时读取。 Please see the console output with inconsistent output marked 有人可以建议我学习什么来自己清除这个错误吗?是时候阅读 JVM 规范了吗?
package com.util.concurrency.tick;
import java.util.concurrent.atomic.AtomicLong;
public class AtomicIncrementer implements Runnable {
private String name;
private static final int MAXI = 1000;
private static final AtomicLong i = new AtomicLong(-1);
public AtomicIncrementer(String name){
this.name = name;
}
public void run(){
while(i.get() < MAXI){
System.out.println(name+ ". i = "
+i.getAndIncrement());
}
System.out.println(name+" i = "+i.get());
}
public static void main(String[] args){;
try {
Thread t1 = new Thread(new AtomicIncrementer("A"));
Thread t2 = new Thread(new AtomicIncrementer("B"));
t1.start();
t2.start();
} catch (Exception e) {
}
System.out.println("Two incrementers launched");
}
}
基本上你没用过同步.
您可以更改 run
方法,如下所示:
public void run(){
synchronized(i){
while(i.get() < MAXI){
System.out.println(name+ ". i = "
+i.getAndIncrement());
}
System.out.println(name+" i = "+i.get());
}
}
此外,您可以参考本教程以了解有关 同步 的更多信息:https://www.tutorialspoint.com/java/java_thread_synchronization.htm
这可能是您想要做的:) 请检查 java 文档。你想在 2 个线程之间同步。基本上每个线程得到 1 轮递增并将计数器传递给另一个线程。
public class AtomicIncrementer implements Runnable {
private static final int MAXI = 1000;
private static final SynchronousQueue<Long> LONG_EXCHANGER = new SynchronousQueue<>();
private final String name;
private AtomicIncrementer(String name) {
this.name = name;
}
@Override
public void run() {
try {
while (true) {
Long counter = LONG_EXCHANGER.take();
if (counter >= MAXI) {
LONG_EXCHANGER.put(counter);
break;
}
System.out.println(name + ". i = " + (counter + 1));
LONG_EXCHANGER.put(counter + 1);
}
Long counter = LONG_EXCHANGER.take();
System.out.println(name + " final i = " + counter);
LONG_EXCHANGER.put(counter);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Thread t1 = new Thread(new AtomicIncrementer("A"));
Thread t2 = new Thread(new AtomicIncrementer("B"));
t1.start();
t2.start();
System.out.println("Two incrementers launched");
try {
LONG_EXCHANGER.put(-1L);
t1.join();
System.out.println("T1 ended");
//this is needed for last thread to end
LONG_EXCHANGER.take();
t2.join();
System.out.println("T2 ended");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
同步不是问题,因为 AtomicLong 就是为了这个。
更确切地说,共享字段必须在线程切换时从 & 更新到本地线程内存。因为存在 volatile
:
private static final volatile AtomicLong i = new AtomicLong(-1);
其实我也不是很确定!对于 long
情况很清楚,
但是只有一个对象;正在更新其长字段。
这里有很多竞争条件。 以下事件顺序将导致您看到的结果:
线程 A 运行到 i=198
。然后是上下文切换,线程 B 运行以下命令:
System.out.println(name+ ". i = "
+i.getAndIncrement());
在 while 循环中。 线程 B 创建字符串:
"B. i = 198"
但是在线程 B 有机会打印该字符串之前,上下文再次切换到线程 A。 所以线程 A 继续执行,直到它打印
A. i=204
然后有一个线程 B 的上下文切换,它从之前停止的地方恢复,它正在打印字符串:
B. i = 198
基本上,在获取 i
的当前值并打印它之前,您有一个竞争条件。
也就是说i.getAndIncrement();
是一个原子操作。
但是,
System.out.println(name+ ". i = "
+i.getAndIncrement());
不是原子操作。
您这里有多项操作。在伪代码中:
1. tempInt = i.getAndIncrement();
2. tempString1 = name + ". i = ";
3. tempString2 = convertToString(tempInt);
4. tempString3 = tempString1 + tempString2;
5. print tempString3;
这就是为什么你的输出如此混乱:)
如果你想深入理解这些概念,我推荐这个在线课程: https://www.udemy.com/java-multithreading-concurrency-performance-optimization/?couponCode=CONCURRENCY
真的很便宜,几个小时就能吃完。但它确实深入探讨了原子 类 的那些概念和注意事项。这是一个很好的时间投资。
在你的情况下你想要做的确实是使用同步而不是 AtomicLong,因为你这里有一个多操作临界区。
希望对您有所帮助。