本文介绍了用AtomicReference代替ReadWriteLock进行非阻塞操作的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我编写了此类,以在持久配置数据发生更改时重新加载整个应用程序使用的DataSource.如您所见,它由CDI管理并显示为Singleton,配置已更改"事件是通过configurationReload(...)方法到达的,但是现在不相关了.

I wrote this class to reload a DataSource, used by the entire application, when the persisted configuration data changes.
As you can see it is managed by CDI and exposed as a Singleton, and the "configuration changed" event arrives through the configurationReload(...) method, but that's not relevant now.

参考更新受ReentrantReadWriteLock保护,但我想知道是否完全需要它.

The reference update is guarded by a ReentrantReadWriteLock, but I'm wondering if it is needed at all.

@Singleton
@ThreadSafe
class ReloadingDataSource implements DataSource {
    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private final Lock readLock = readWriteLock.readLock();
    private final Lock writeLock = readWriteLock.writeLock();

    @GuardedBy("readWriteLock")
    private DataSource delegateDataSource;

    @Inject
    ReloadingDataSource(@Nonnull final Configuration configuration) {
        delegateDataSource = createDataSource(configuration);
    }

    private DataSource createDataSource(final Configuration configuration) {
        ... Create a ComboPooledDataSource using properties extracted from Configuration.
    }

    @Override
    public Connection getConnection() throws SQLException {
        readLock.lock();

        try {
            return delegateDataSource.getConnection();
        } finally {
            readLock.unlock();
        }
    }

    ...

    private void configurationReload(
            @Observes @Reload final ConfigurationChanged configurationChanged,
            @Nonnull final Configuration configuration) {
        final ConfigurationEvent event = configurationChanged.getConfigurationEvent();

        if (event.getType() != AbstractFileConfiguration.EVENT_RELOAD && !event.isBeforeUpdate()) {
            return;
        }

        writeLock.lock();

        try {
            destroyDelegateDataSource();
            delegateDataSource = createDataSource(configuration);
        } finally {
            writeLock.unlock();
        }
    }

    private void destroyDelegateDataSource() {
        try {
            DataSources.destroy(delegateDataSource);
        } catch (final SQLException ignored) {
            // Do nothing.
        }
    }
}

如果我们忽略了创建新数据源的成本,是否可以将上述策略替换为AtomicReference<DataSource>,如下所示?这将导致更好的性能和更易于阅读的代码.

If we ignore the cost of creating a new DataSource, could the above strategy be substituted by an AtomicReference<DataSource>, as below?
It would result in better performing and easier to read code.

还有我不知道的更好的方法来处理此问题吗?

Are there better ways to handle this which I'm not aware of?

@Singleton
@ThreadSafe
class ReloadingDataSource implements DataSource {
    private final AtomicReference<DataSource> delegateDataSource;

    @Inject
    ReloadingDataSource(@Nonnull final Configuration configuration) {
        delegateDataSource = new AtomicReference<>(createDataSource(configuration));
    }

    private DataSource createDataSource(final Configuration configuration) {
        ... Create a ComboPooledDataSource using properties extracted from Configuration.
    }

    @Override
    public Connection getConnection() throws SQLException {
        return delegateDataSource.get().getConnection();
    }

    ...

    private void configurationReload(
            @Observes @Reload final ConfigurationChanged configurationChanged,
            @Nonnull final Configuration configuration) {
        final ConfigurationEvent event = configurationChanged.getConfigurationEvent();

        if (event.getType() != AbstractFileConfiguration.EVENT_RELOAD && !event.isBeforeUpdate()) {
            return;
        }

        // Updated as per eckes tip. Is this what you meant?
        final DataSource newDataSource = createDataSource(configuration);

        while (true) {
            final DataSource oldDataSource = delegateDataSource.get();

            if (delegateDataSource.compareAndSet(oldDataSource, newDataSource)) {
                destroyDelegateDataSource(oldDataSource);
                break;
            }
        }
    }

    private void destroyDelegateDataSource(final DataSource oldDataSource) {
        try {
            DataSources.destroy(oldDataSource);
        } catch (final SQLException ignored) {
            // Do nothing.
        }
    }
}

推荐答案

如果需要以有序方式处理更新,则仍需要锁定reload方法.在这种情况下,您可以放弃AtomicReference逻辑,而只需使用volatile:

If you require your updates to be handled in an ordered fashion, you still need locking on the reload method. In which case, you can ditch the AtomicReference logic and just go with a volatile:

public class RDS {
  private volatile DataSource delegate;

  public Connection getConnection() throws SQLException {
    return delegate.getConnection();
  }

  private void reload(Configuration config) {
    DataSource old = null;
    synchronized(this) {
      old = delegate;
      delegate = createDataSource(config);
    }
    destroyDataSource(old);
  }
}

但是请注意,关闭旧数据源时,仍然可能存在其他问题,连接仍然可以用于旧的数据源(在@eckes关于该问题的第一条评论中提到).为了解决这个问题,您需要一个带有获取/释放"类型逻辑的连接池,一旦释放了所有现有连接,该连接池就会关闭旧的委托.

Note however, you still potentially have other issues where connections could still be in use for the old DataSource when you close it (mentioned in @eckes first comment on the question). In order to fix that, you need something like a connection Pool with acquire/release type logic which closes the old delegate once all existing connections have been released.

这篇关于用AtomicReference代替ReadWriteLock进行非阻塞操作的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

10-15 14:19