Java进程消除竞态条件,Java 并发:使用 AtomicReference 处理竞态条件

java.util.concurrent.atomic.AtomicReference 类的设计可以保证用线程安全的方式更新变量。为什么需要 AtomicReference?为什么不用 volatile 变量达成目标?如何正确地使用 AtomicReference?

为什么要用 AtomicReference?

我正在开发一个工具软件,需要检测某个对象是否被多个线程调用。为此我用到了以下的不可变类:

public class State {

private final Thread thread;

private final boolean accessedByMultipleThreads;

public State(Thread thread, boolean accessedByMultipleThreads){

super();

this.thread = thread;

this.accessedByMultipleThreads = accessedByMultipleThreads;

}

public State(){

super();

this.thread = null;

this.accessedByMultipleThreads = false;

}

public State update(){

if(accessedByMultipleThreads)   {

return this;

}

if( thread == null  ) {

return new  State(Thread.currentThread()

, accessedByMultipleThreads);

}

if(thread != Thread.currentThread()) {

return new  State(null,true);

}

return this;

}

public boolean isAccessedByMultipleThreads(){

return accessedByMultipleThreads;

}

}

第2行,thread 变量中存放了访问对象的第一个线程。第23行,当有其它线程访问该对象时,把 accessedByMultipleThreads 变量设为 true,并把 thread 变量置为 null。第15至17行,当 accessedByMultipleThreads 变量为 true 时,不改变 state。

在每个对象中使用这个类,检查是否被多个线程访问。下面的 UpdateStateNotThreadSafe 展示了如何使用 state:

public class UpdateStateNotThreadSafe {

private volatile  State state = new State();

public void update(){

state = state.update();

}

public State getState(){

return state;

}

}

第2行,把 state 存入 volatile 变量。这里需要使用 volatile 关键字确保线程始终能够看到当前值。

下面这个测试用来检查 volatile 变量是否线程安全:

import com.vmlens.api.AllInterleavings;

public class TestNotThreadSafe {

@Test

public void test() throws InterruptedException{

try (AllInterleavings allInterleavings =

new AllInterleavings(“TestNotThreadSafe”);){

while (allInterleavings.hasNext()) {

final UpdateStateNotThreadSafe object  = new UpdateStateNotThreadSafe();

Thread first = new Thread(() -> { object.update(); });

Thread second = new Thread(() -> { object.update(); });

first.start();

second.start();

first.join();

second.join();

assertTrue(object.getState().isAccessedByMultipleThreads());

}

}

}

}

第9至10行创建了两个线程,用来测试在 volatile 变量是否线程安全。第11至12行启动这两个线程,直到两个线程调用线程 join 操作结束(第13至14行)。第15行,当两个线程停止后,检查 accessedByMultipleThreads 是否为 true。

为了测试所有线程的交叉情况,第7行的 while 循环会遍历 AllInterleavings 类中所有 interleavingvmlens。(译注:interleaving 是 vmlens 开发库多线程测试中的概念)运行测试看到以下错误:

java.lang.AssertionError:

at org.junit.Assert.fail(Assert.java:91)

at org.junit.Assert.assertTrue(Assert.java:43)

at org.junit.Assert.assertTrue(Assert.java:54)

vmlens 的报告揭示了问题所在:

ed80d2364f75249102e65fea4bd93422.png

这里的问题在于,对于特定线程两个线程会首先交叉读取 state。因此,一个线程会覆盖另一个线程的结果。

如何使用 AtomicReference?

为了解决这种竞态条件,我使用 AtomicReference 的 compareAndSet 方法。

compareAndSet 方法有两个参数,期望值和更新值。该方法会自动检测当前值与期望值是否相等。如果相等,会设置为更新值并返回 true。如果不等,则当前值保持不变并返回 false。

这种方法的主要思想,通过 compareAndSet 检查计算新值的过程中,当前值是否被另一个线程修改。如果没有,可以安全地更新当前值。否则,需要使用更改后的当前值重新计算新值。

下面显示了如何使用 compareAndSet 方法自动更新 state:

public class UpdateStateWithCompareAndSet {

private final AtomicReference state = new AtomicReference(new State());

public  void update(){

State current = state.get();

State newValue = current.update();

while( ! state.compareAndSet( current , newValue ) ) {

current = state.get();

newValue = current.update();

}

}

public State getState(){

return state.get();

}

}

第2行,对 state 变量使用了AtomicReference。第5行,要更新 state 首先需要获取当前值。然后,在第6行计算新值,并在第7行尝试使用 compareAndSet 更新 AtomicReference。如果更新成功,则处理完毕。如果没有成功,需要在第8行再次获取当前值,并在第9行重新计算新值。然后,再次尝试使用 compareAndSet 更新 AtomicReference。使用 while 循环是因为 compareAndSet 可能会多次失败。

总结

使用 volatile 变量会引发竞态条件,因为某个特定线程的交叉访问会覆盖其它线程的计算结果。通过使用 AtomicReference 类中的 compareAndSet 方法可以规避这种竞态条件。通过这种方法,可以自动检查当前值是否与开始计算时相同。如果相同,可以安全地更新当前值。否则,需要用更改后的当前值重新计算新值。

可以从 GitHub 上下载所有示例源代码。

github.com/vmlens/tutorial-atomic-reference

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值