此类使用 AtomicBooleans。它是线程安全的吗?
Posted
技术标签:
【中文标题】此类使用 AtomicBooleans。它是线程安全的吗?【英文标题】:This class uses AtomicBooleans. Is it thread safe? 【发布时间】:2009-05-28 04:54:02 【问题描述】:我不喜欢用 synchronized(this) 来锁定我的代码,所以我正在尝试使用 AtomicBooleans。在代码 sn-p 中,XMPPConnectionIF.connect() 建立到远程服务器的套接字连接。请注意,变量 _connecting 只在 connect() 方法中使用过;而 _connected 用于需要使用 _xmppConn 的所有其他方法。我的问题列在下面的代码 sn-p 之后。
private final AtomicBoolean _connecting = new AtomicBoolean( false );
private final AtomicBoolean _connected = new AtomicBoolean( false );
private final AtomicBoolean _shuttingDown = new AtomicBoolean( false );
private XMPPConnection _xmppConn;
/**
* @throws XMPPFault if failed to connect
*/
public void connect()
// 1) you can only connect once
if( _connected.get() )
return;
// 2) if we're in the middle of completing a connection,
// you're out of luck
if( _connecting.compareAndSet( false, true ) )
XMPPConnectionIF aXmppConnection = _xmppConnProvider.get();
boolean encounteredFault = false;
try
aXmppConnection.connect(); // may throw XMPPException
aXmppConnection.login( "user", "password" ); // may throw XMPPException
_connected.compareAndSet( false, true );
_xmppConn = aXmppConnection;
catch( XMPPException xmppe )
encounteredFault = true;
throw new XMPPFault( "failed due to", xmppe );
finally
if( encounteredFault )
_connected.set( false );
_connecting.set( false );
else
_connecting.compareAndSet( true, false );
根据我的代码,如果 2 个线程尝试同时调用 connect(),那么线程是否安全,只允许一次连接尝试。
在finally块中,我连续执行了两个AtomicBoolean.set(..),会不会有问题,因为在这两个原子调用之间的间隙,一些线程可能会调用_connected .get() 在其他方法中?
当使用_xmppConn时,我应该做一个同步(_xmppConn)吗?
更新在方法中添加了缺少的登录调用。
【问题讨论】:
【参考方案1】:请记住,使用 3 个AtomicBoolean
s 不与使用单个锁保护这三个变量相同。在我看来,这些变量的状态构成了对象的单一状态,因此它们应该由同一个锁保护。在您使用原子变量的代码中,不同的线程可以独立更新_connected
、_connecting
和_shuttingDown
的状态——使用原子变量只会确保访问相同变量在多个线程之间同步。
也就是说,我不认为在this
上同步是您想要做的。您只想同步访问连接状态。您可以做的是创建一个对象用作此状态的锁,而无需在this
上获取监视器。即:
class Thing
Boolean connected;
Boolean connecting;
Boolean shuttingDown;
Object connectionStateLock = new Object();
void connect()
synchronized (connectionStateLock)
// do something with the connection state.
void someOtherMethodThatLeavesConnectionStateAlone()
// free range thing-doing, without getting a lock on anything.
如果您正在使用 Java 进行并发编程,我强烈建议您阅读 Java Concurrency In Practice。
【讨论】:
是的,我在我的脑海里有一种唠叨的感觉,我没有正确地做这件事。那就是你进来的地方!我可以将三个布尔值包装在一个对象中并将其放入 AtomicReference 中。那行得通吗?或者我也可以在方法上同步(这个)(这是我想要避免的)。 除了锁定this
之外,我已经编辑了评论以包含一个选项。【参考方案2】:
是的。变量 _connecting 充当测试和设置锁,防止多个并发连接尝试。
没问题 - 即使另一个线程在写入之间读取 _connected,_connecting 也会阻止它尝试并发连接。
是的,假设它的方法还不是线程安全的。
话虽如此,您的 connect() 方法会以目前的形式让我抓狂,因为它不一定连接或抛出异常。您可以添加一个自旋循环,但这并不是一个很好的选择,因为除了来自多处理器机器的最短网络跳数之外,它会更有效地产生。此外,低级并发原语比同步更容易出错 - 我强烈建议您坚持使用同步。
【讨论】:
我忘了在代码中添加实际的登录语句。它确实会为连接失败抛出 XMPPFault。【参考方案3】:我认为其他人在他们的 cmets 中充分涵盖了正确性。我唯一的补充意见是我有点担心发布在 finally 中的位置。似乎您可能真的想将整个块(包括 _xmppConnProvider.get() 调用)包装在 try finally 中,这将保证您始终释放锁。否则,那里可能会发生某种未经检查的异常并使您处于不可恢复的状态。
在风格上,我认为这段代码比简单地使用 synchronized/Lock 来实现互斥更难推理。我会从易于推理的代码开始,如果你能证明这是一个热点,只会让它变得更复杂。
【讨论】:
【参考方案4】:我怀疑你的程序是线程安全的。我不是 Java 内存模型专家,但据我所知,可以安排操作,并且操作结果可能无法按您期望的顺序对其他线程可见。
考虑例如将 _connected 设置为 true 是否在 connect() 方法完全执行之前执行?另一个线程可能认为您已连接,即使您没有连接。这只是推测 - 我不确定是否会发生特定问题。
我的意思是,您尝试做的那种锁定是非常棘手的。坚持同步或使用 java.util.concurrent.locks 包中的锁。
【讨论】:
aXmppConnection.login(..) 返回(无异常)后,connect() 将被视为完成。【参考方案5】:是的,它绝对满意。因为 _connecting.compareAndSet( false, true ) 将只允许一个线程进入。
你不需要设置 _connected.set( false );因为如果发生异常,它永远不会设置为 true。是的,它可能不是由于连续,而是直到你没有设置连接到错误,其他尝试连接的线程不会认为连接正在进行中。
是,如果 xmppConn 不是线程安全的。
【讨论】:
以上是关于此类使用 AtomicBooleans。它是线程安全的吗?的主要内容,如果未能解决你的问题,请参考以下文章
java 此类表示线程安全堆栈并使用CAS指令来确保线程安全。