我得到了这样的代码:
public class ConcurrencyCheck {
private volatile static AtomicBoolean top=new AtomicBoolean(false);
private int i=0;
public class Toppler extends Thread{
private final boolean bool;
public Toppler(boolean myBool,String name) {
super(name);
bool=myBool;
}
@Override
public void run() {
while(!isInterrupted()){
i++;
synchronized (top) {
if (top.get() == bool) top.set(!top.get());
System.err.println(super.getName() + " "+ bool +"->" + top + ". i is " + i);
}
try {
sleep(100);
} catch (InterruptedException e) {
break;
}
}
}
}
public static void main(String[] args) throws InterruptedException {
ConcurrencyCheck cc = new ConcurrencyCheck();
Thread t1= cc.new Toppler(true,"thread1");
Thread t2= cc.new Toppler(false,"thread2");
Thread t3= cc.new Toppler(true,"thread3");
Thread t4= cc.new Toppler(false,"thread4");
Thread t5= cc.new Toppler(true,"thread5");
Thread t6= cc.new Toppler(false,"thread6");
Thread t7= cc.new Toppler(true,"thread7");
Thread t8= cc.new Toppler(false,"thread8");
t1.start();
...
t8.start();
sleep(950);
t1.interrupt();
...
t8.interrupt();
}
}
Run Code Online (Sandbox Code Playgroud)
它旨在检查 AtomicBoolean 的工作方式。Toppler 类是一个线程,它会定期推翻 Atomic 布尔值。推翻布尔值的代码块是同步的。正如我猜测的那样,每个输出行都必须推翻“top”变量的值,因此输出必须是“true->false false->true true->false...”。但出于某种原因,有时我可以看到这样的输出:
thread1 true->false. i is 1
thread8 false->true. i is 8
thread7 true->false. i is 8
thread4 false->true. i is 8
thread6 false->true. i is 8
thread3 true->false. i is 8
thread5 true->false. i is 8
thread2 false->true. i is 8
thread1 true->false. i is 9
thread8 false->true. i is 10
thread7 true->false. i is 11
thread4 false->true. i is 12
thread6 false->true. i is 13
thread3 true->false. i is 14
Run Code Online (Sandbox Code Playgroud)
问题是:为什么可能出现两个随后的 false->true 或 true->false 输出行?
你问:
\n\n\n问题是:为什么两个后续的 false->true 或 true->false 输出行是可能的?
\n
两个原因:
\nSystem.err.println
我怀疑对之类的调用System.out.println
并不总是按时间顺序出现在控制台上。\xe2\x9e\xa5 如果您想要协调线程之间的活动,例如以特定模式交替它们的工作,则必须执行其他步骤。
\n我不太清楚你的实验代码的目标。显然,每个任务都是用目标布尔值构建的。如果该任务找到当前标志变量top
与我们的目标布尔值匹配,那么我们就会翻转top
到相反的状态。
至于线程按 1、8、7、4 等编号顺序交错,这是可以预料的。无法预测线程的执行顺序。接下来安排哪个线程在 CPU 核心上执行以及执行多长时间取决于 JVM 和主机操作系统,具体取决于它们的算法和当前运行时条件。永远不要期望一组线程按特定顺序运行。
\n您的代码有几个问题。
\n一是您正在增加一个没有并发保护的共享int
变量。i
您将该int
var 声明为类成员ConcurrencyCheck
,然后i++
在每个线程中调用。该代码递增i
不是线程安全的,跨线程共享可变资源。这就是为什么你看到那里的价值观失败的原因。您应该使用 来AtomicInteger
增加计数器的目的。
关于synchronized (top) {
,无需将该代码标记synchronized
为您当前编写的代码。AtomicBoolean
使用而不是仅仅使用的原因Boolean
是为了自动线程安全,从而消除对synchronized
. 然而,您试图同时做一些其他事情:增加计数器,并用当前值报告您的进度。因此,出于这个原因,要将多个其他操作以原子方式组合在一起,您确实需要 a synchronized
,但您不需要简单地单独synchronized
使用a 。AtomicBoolean
关于if (top.get() == bool) top.set(!top.get());
,您应该使用其中一种复杂的方法以AtomicBoolean
原子方式执行获取和设置类型的操作。AtomicBoolean
这就是使此类操作原子化的工作。具体来说,我相信您希望AtomicBoolean#compareAndExchange
在找到预期值时设置一个新值,然后返回找到的值。
您用于报告结果的消息字符串令人困惑。我会写一些更像这样的东西,其中你的bool
var 被重命名为expectedValue
Javadoc for 的每个术语compareAndExchange
。警告:我不是这方面的专家,因此请验证我的逻辑并验证我对该 Javadoc 的理解。
boolean witnessValue = top.compareAndExchange( this.expectedValue , ! this.expectedValue );\nString msg = "Thread ID # " + Thread.currentThread().getId() + " expected: " + this.expectedValue + ", found: " + witnessValue + ", ended with top being: " + witnessValue + ". countAttempts: " + countAttempts + "." + " Now: " + Instant.now();\n
Run Code Online (Sandbox Code Playgroud)\n关于private volatile static AtomicBoolean top
,这里不需要标记AtomicBoolean
为volatile
。通过初始化声明的位置,您可以确保已经AtomicBoolean
分配了一个对象。您永远不会用另一个对象替换该对象,因此任何线程都不可能出现 CPU 核心缓存具有不同值的可见性问题。您应该标记该字段,final
以防止错误地换出另一个对象。
另一个问题是,在现代 Java 中,我们很少需要Thread
直接处理类。Java 5 中添加了执行器服务框架,以减轻我们处理线程的繁琐工作。将您的任务定义为Runnable
( 或Callable
),并将实例提交给ExecutorService
您选择的实现(请参阅Executors
类)。
不要期望控制台输出按时间顺序排列。至于 上的输出顺序System.err
,我想System.out
它不是按时间顺序排列的。我不知道这是由于在括号内生成的消息内容println
与实际传递给println
方法之间的线程被挂起,还是由于内部缓冲问题,但我可以告诉你,我经常看到一系列控制台上未按println
时间顺序出现的呼叫。我建议 (a) 始终包含对或 的调用,以及 (b) 在线程安全的.Instant.now
System.nanoTime
List
归档时间: |
|
查看次数: |
64 次 |
最近记录: |