此类使用 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 个AtomicBooleans 与使用单个锁保护这三个变量相同。在我看来,这些变量的状态构成了对象的单一状态,因此它们应该由同一个锁保护。在您使用原子变量的代码中,不同的线程可以独立更新_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指令来确保线程安全。

不会吧,你连Java 多线程线程安全都还没搞明白,难怪你面试总不过

Linux线程安全篇Ⅱ

+= 运算符在 Python 中是线程安全的吗?

Java并发/多线程系列——线程安全篇

Java并发/多线程系列——线程安全篇