Ограничение одновременного доступа к методу - PullRequest
1 голос
/ 29 марта 2011

У меня проблема с ограничением одновременного доступа к методу. У меня есть метод MyService, который можно вызывать из разных мест много раз. Этот метод должен возвращать String, который должен быть обновлен в соответствии с некоторыми правилами. Для этого у меня есть updatedString класс. Прежде чем получить String, он проверяет, обновлен ли String, если нет, то обновляет его. Многие потоки могут одновременно читать String, но ТОЛЬКО ОДИН должен обновлять String одновременно, если он устарел.

public final class updatedString {

private static final String UPstring;
private static final Object lock = new Object();

public static String getUpdatedString(){
    synchronized(lock){
        if(stringNeedRenewal()){
           renewString();
        }
    }
    return getString();
}

...

Это отлично работает. Если у меня 7 потоков, получающих строку, это гарантирует, что при необходимости ТОЛЬКО один поток обновляет строку.

Мой вопрос: хорошая ли идея иметь все это static? Почему если нет? Это быстро? Есть ли лучший способ сделать это?

Я читал такие сообщения: В каких случаях требуется синхронный доступ к методам в Java? , что говорит о том, что статические изменяемые переменные не являются хорошей идеей, а также статическими классами. Но я не вижу никакой тупиковой ситуации в коде или лучшего решения. Только то, что некоторым потокам придется ждать обновления String (при необходимости) или ждать, пока другой поток покинет синхронизированный блок (что вызывает небольшую задержку).

Если метод не static, то у меня есть проблема, потому что это не будет работать, поскольку синхронизированный метод действует только для текущего экземпляра, который использует поток. Синхронизированный метод тоже не работает, похоже, что блокировка зависит от экземпляра, а не от класса. Другим решением может быть использование Singleton, который избегает создания более одного экземпляра, а затем использует один синхронизированный нестатический класс, но мне это решение не очень нравится.

Дополнительная информация:

stringNeedRenewal() не слишком дорого, хотя он должен читать из базы данных. renewString() напротив, это очень дорого, и приходится читать из нескольких таблиц в базе данных, чтобы наконец прийти к ответу. String требует произвольного обновления, но это происходит не очень часто (от одного раза в час до одного раза в неделю).

@ Форсварир заставил меня задуматься ... и я думаю, он был прав. return getString(); ДОЛЖЕН быть внутри синхронизированного метода. На первый взгляд это выглядит так, как будто это может быть вне его, поэтому потоки смогут читать его одновременно, но что произойдет, если поток прекратит работу, вызывая getString(), а другой поток частично выполнит renewString()? Мы могли бы иметь такую ​​ситуацию (при условии, что один процессор):

  1. THREAD 1 запускается getString(). ОС начинает копировать в память байты подлежит возврату.
  2. ОС THREAD 1 останавливается перед завершением копирования.

  3. THREAD 2 поступает в синхронизированный блокировка и запуск renewString(), изменение оригинала String в память.

  4. THREAD 1 возвращает контроль и закончите getString, используя поврежден String !! Так он скопировал один часть из старой строки и другой от нового.

Чтение внутри синхронизированного блока может сделать все очень медленно, поскольку потоки могут обращаться к нему только один за другим.

Как отметил @ Джереми Хейлер, это абстрактная проблема кеша. Если кэш старый, обновите его. Если нет, используйте его. Лучше представить себе проблему, подобную этой, вместо одного String (или представьте, что вместо одной есть 2 строки). Так что же происходит, если кто-то читает в то же самое время, когда кто-то модифицирует кеш?

Ответы [ 4 ]

3 голосов
/ 29 марта 2011

Прежде всего, вы можете снять блокировку и синхронизированный блок и просто использовать:

public static synchronized String getUpdatedString(){
    if(stringNeedRenewal()){
       renewString();
    }
    return getString();
}

, это синхронизирует объект UpdatedString.class.

Еще одна вещь, которую вы можете сделать, этоиспользуется двойная проверка блокировки, чтобы избежать ненужного ожидания.Объявите строку как volatile и:

public static String getUpdatedString(){
    if(stringNeedRenewal()){
        synchronized(lock) {
            if(stringNeedRenewal()){
                renewString();
            }
        }
    }
    return getString();
}

Затем, использовать статическую или нет - кажется, что она должна быть статической, поскольку вы хотите вызывать ее без какого-либо конкретного экземпляра.

1 голос
/ 29 марта 2011

Я бы посоветовал изучить ReentrantReadWriteLock .(Независимо от того, является ли он производительным, решать вам.) Таким образом, вы можете одновременно выполнять много операций чтения.

Вот пример из документации:

 class CachedData {
   Object data;
   volatile boolean cacheValid;
   ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

   void processCachedData() {
     rwl.readLock().lock();
     if (!cacheValid) {
        // Must release read lock before acquiring write lock
        rwl.readLock().unlock();
        rwl.writeLock().lock();
        // Recheck state because another thread might have acquired
        //   write lock and changed state before we did.
        if (!cacheValid) {
          data = ...
          cacheValid = true;
        }
        // Downgrade by acquiring read lock before releasing write lock
        rwl.readLock().lock();
        rwl.writeLock().unlock(); // Unlock write, still hold read
     }

     use(data);
     rwl.readLock().unlock();
   }
 }
0 голосов
/ 30 марта 2011

Это не совсем то, что вам нужно, и я не специалист по Java, поэтому возьмите это с собой щепотку соли:)

Возможно, пример кода, который вы предоставили, придуман, но если нет, мне неясно, какова цель этого класса. Вы хотите, чтобы только один поток обновил строку до ее нового значения. Зачем? Это для экономии усилий (потому что вы предпочитаете использовать циклы процессора на чем-то другом)? Это для поддержания согласованности (после достижения определенной точки строка должна быть обновлена)?

Как долго длится цикл между необходимыми обновлениями?

Глядя на ваш код ...

public final class updatedString {

private static final String UPstring;
private static final Object lock = new Object();

public static String getUpdatedString(){
    synchronized(lock){
        // One thread is in this block at a time
        if(stringNeedRenewal()){
           renewString();  // This updates the shared string?
        }
    }
    // At this point, you're calling out to a method.  I don't know what the
    // method does, I'm assuming it just returns UPstring, but at this point, 
    // you're no longer synchronized.  The string actually returned may or may  
    // not be the same one that was present when the thread went through the 
    // synchronized section hence the question, what is the purpose of the
    // synchronization...
    return getString();  // This returns the shared string?
}

Правильная блокировка / оптимизация зависят от причины, по которой вы их устанавливаете, вероятности необходимости записи и, как сказал Пауло, стоимости операций.

В некоторых ситуациях, когда записи выполняются редко, и, очевидно, в зависимости от того, что делает renewString, может быть желательно использовать оптимистический подход к записи. Когда каждый поток проверяет, требуется ли обновление, выполняет обновление локально, а затем только в конце, присваивает значение для читаемого поля (вам нужно отслеживать возраст ваших обновлений, если вы следуете этому подходу) , Это будет быстрее для чтения, так как проверка «нуждается ли строка в обновлении» может быть выполнена за пределами синхронизированного раздела. Различные другие подходы могут быть использованы, в зависимости от индивидуального сценария ...

0 голосов
/ 29 марта 2011

пока вы блокируете статично, все остальное не должно быть, и все будет работать так же, как сейчас

...