2

我不喜欢用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 回答 5

6

请记住,使用 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

于 2009-05-28T05:11:16.967 回答
5
  1. 是的。变量 _connecting 充当测试和设置锁,可防止多个并发连接尝试。

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

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

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

于 2009-05-28T05:13:23.423 回答
2

我认为其他人在他们的评论中充分涵盖了正确性。我唯一的补充意见是我有点担心发布在 finally 中的位置。似乎您可能真的想将整个块(包括 _xmppConnProvider.get() 调用)包装在 try { } finally { } 中,这将保证您始终释放锁。否则,那里可能会发生某种未经检查的异常并使您处于不可恢复的状态。

从风格上讲,我认为这段代码比简单地使用 synchronized/Lock 来实现互斥更难推理。我会从易于推理的代码开始,如果你能证明这是一个热点,只会让它变得更复杂。

于 2009-05-28T13:56:21.503 回答
1

我怀疑您的程序将是线程安全的。我不是 Java 内存模型大师,但据我所知,可以安排操作,并且操作结果可能无法按您期望的顺序对其他线程可见。

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

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

于 2009-05-28T06:55:19.013 回答
0
  1. 是的,它绝对满意。因为 _connecting.compareAndSet( false, true ) 将只允许一个线程进入。

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

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

于 2009-05-28T05:36:58.370 回答