Замените ReadWriteLock на AtomicReference для неблокирующих операций - PullRequest
0 голосов
/ 08 октября 2018

Я написал этот класс для перезагрузки DataSource, используемого всем приложением, при изменении постоянных данных конфигурации.
Как вы можете видеть, он управляется CDI и отображается как Singleton, исобытие «конфигурация изменена» приходит с помощью метода configurationReload(...), но сейчас это не актуально.

Обновление ссылки защищено ReentrantReadWriteLock, но мне интересно, нужно ли оно вообще.

@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>, как показано ниже?
Это приведет к повышению производительности илегче читать код.

Есть ли лучшие способы справиться с этим, о которых я не знаю?

@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.
        }
    }
}

1 Ответ

0 голосов
/ 08 октября 2018

Если вам требуется, чтобы ваши обновления обрабатывались упорядоченным образом, вам все равно нужно заблокировать метод перезагрузки.В этом случае вы можете отказаться от логики AtomicReference и просто использовать 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 первый комментарий к вопросу).Чтобы это исправить, вам нужно что-то вроде пула соединений с логикой типа acqu / release, которая закрывает старый делегат после освобождения всех существующих соединений.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...