本课程使用AtomicBooleans。它是线程安全的吗?

时间:2009-05-28 04:54:03

标签: java multithreading concurrency xmpp smack

我不想用 synchronized(this)来锁定我的代码,所以我正在尝试使用 AtomicBooleans 。在代码段中, XMPPConnectionIF.connect()与远程服务器建立套接字连接。请注意,变量 _connecting 仅在 connect()方法中使用;而 _connected 用于需要使用 _xmppConn 的其他所有方法。我的问题列在下面的代码段之后。

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 );
        }
    }
}
  1. 根据我的代码,如果2个线程同时尝试调用 connect(),那么线程是否安全,只允许一次连接尝试。

  2. 在finally块中,我连续执行两个AtomicBoolean.set(..),会出现问题,因为在这两个原子调用之间的间隙中,某些线程可能会调用 _connected .get()在其他方法中?

  3. 使用 _xmppConn 时,我应该执行 synchronized(_xmppConn)吗?

  4. 更新在方法中添加了缺少的登录调用。

5 个答案:

答案 0 :(得分:6)

请注意,使用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

答案 1 :(得分:5)

  1. 是。变量_connecting充当测试和设置锁,可防止多个并发连接尝试。

  2. 没问题 - 即使另一个线程在写入之间读取_connected,_connecting也会阻止它同时尝试连接。

  3. 是的,假设它的方法不是线程安全的。

  4. 话虽这么说,你的connect()方法会以当前的形式让我疯狂,因为它不一定连接或抛出异常。你可以添加一个旋转循环,但这并不是一个很好的选择,因为除了多处理器机器中最短的网络跳跃之外,它的产生效率会更高。此外,低级并发原语比同步更容易出错 - 我 强烈 建议您坚持使用synchronized。

答案 2 :(得分:2)

我认为其他人在评论中充分涵盖了正确性。我唯一的另一个评论是,我关注的是在最终版本中放置版本。看起来你可能真的想在try {} finally {}中包装整个块(包括_xmppConnProvider.get()调用),这将保证你总是释放锁。否则,某种未经检查的异常可能会在那里发生并使您处于不可恢复的状态。

从风格上讲,我认为这个代码比单纯使用synchronized / Lock实现互斥更难以推理。我会从易于推理的代码开始,只有当你能证明这是一个热点时才会使它变得更复杂。

答案 3 :(得分:1)

我怀疑你的程序是否是线程安全的。我不是Java内存模型大师,但是根据我所学到的操作,可以安排操作,并且操作结果可能不会按照您期望的顺序显示给其他线程。

考虑是否在完全执行connect()方法之前执行设置 _connected 为true?即使你不是,另一个线程可能会认为你是连接的。这只是猜想 - 我不确定特定问题是否会发生。

我的观点是,你想要做的那种锁定是非常棘手的。坚持使用synchronized或使用 java.util.concurrent.locks 包中的锁。

答案 4 :(得分:0)

  1. 是的,它绝对满意。 as _connecting.compareAndSet(false,true)将只允许一个线程进入。

  2. 您不需要设置_connected.set(false);因为如果发生异常,它永远不会设置为true。是的,它可能不是由于继承,但直到你没有设置连接到false其他尝试连接的线程不会认为连接正在进行中。

  3. 是的,如果xmppConn不是线程安全的话。