Как я могу убедиться, что метод вызывается только один раз несколькими потоками?

У меня есть следующая структура:

public void someMethod(){  
   //DO SOME STUFF
   try{  
    doSomeProcessing();  
   }  
   catch (Exception e){  
        loadSomeHeavyData();  
        doSomeProcessing();      
   }    
}  

МетодsomeMethod может быть вызванным одновременно многими потоками.doSomeProcessing может генерировать исключение (оно использует некоторые данные в бэкэнде, которые могут устареть).

Если выдается исключение, тоloadSomeHeavyData(); выполняет некоторое трудоемкое задание, которое позволяетс "обновления» все текущие данные и я могу позвонить.doSomeProcessing();

Проблема: Как я могу убедиться, чтоloadSomeHeavyData(); называется толькоодин раз? Если я поставлю какой-то атомный флаг в записиloadSomeHeavyData(); тогда я не могу быть уверен, когда это должно быть очищено.

Как я могу решить это? Просто примечание: я не могу изменитьdoSomeProcessing(); так как это внешний API, и я использую шаблон декоратора, чтобы использовать его.

 Azodious13 нояб. 2012 г., 08:29
@Jim: вместо звонкаloadSomeHeavyData вcatch, поставьте запрос на вызов в очередь. Это'Это изменение дизайна, но лучше соответствует вашим требованиям. проверьте мой ответ ниже.
 Jim13 нояб. 2012 г., 08:11
@scarcer: Совершенно верно.
 Jim13 нояб. 2012 г., 08:25
@JonSkeet: Если / когда он снова брошен, я хотел быloadSomeHeavyData() быть вызванным первым потоком, который может вызвать API. Многие потоки могут получить одно и то же исключение одновременно, но только один из них снова вызоветloadSomeHeavyData весь путь
 Jim13 нояб. 2012 г., 08:20
@Azodious: После того, как выдается исключение иloadSomeHeavyData() Выполнение исключения не ожидается, если пользователь не изменил конфигурацию или данные. Так не скоро
 Jon Skeet13 нояб. 2012 г., 08:22
@Jim: Но что бы вы хотели случиться, если бы исключениеявляется бросили снова? Это'Важно точно учитывать все требования.
 scarcer13 нояб. 2012 г., 08:07
Почему вы хотите очистить атомный флагloadSomeHeavyData()? Я думаю, что вы просто хотите загрузить эти данные один раз?
 Azodious13 нояб. 2012 г., 08:05
How can I make sure that loadSomeHeavyData(); is called only once? Не очень понятно Вы хотите, если один поток вызываетloadSomeHeavyDataдругие темы не должныВы называете это даже после возникновения исключения?
 Azodious13 нояб. 2012 г., 08:15
Один вопрос: когда следуетloadSomeHeavyData(); позвонить снова? то есть откуда ты знаешь, что этовремя обновить данные, даже если они были обновлены ранее.
 Jim13 нояб. 2012 г., 08:10
@Azodious: либо то, либо другоеПонимаю" что тяжелый API уже был вызван, и данные будут обновляться для них также первым потоком
 Jon Skeet13 нояб. 2012 г., 08:03
однаждыКогда-либоили просто избегать одновременных звонков?
 Jim13 нояб. 2012 г., 08:09
@JonSkeet: я не могу избежать одновременных вызовов. Но исключение будет выдано, и хотелось бы, чтобы только один из потоков вызывал тяжелый API.

Ответы на вопрос(5)

полезность лениво загрузить / вызвать метод. Это гарантирует семантику однократного использования и сохраняет любые сгенерированные исключения как выбуду ожидать.

Использование простое:

LazyReference<thing> heavyThing = new LazyReference<thing>() {
  protected Thing create() {
    return loadSomeHeavyData();
  }
};

public void someMethod(){  
  //DO SOME STUFF
  try{  
    doSomeProcessing();  
  }  
  catch (Exception e){  
    heavyThing.get();  
    doSomeProcessing();      
  }    
}  
</thing></thing>

Все темы блокируются наget() и дождитесь завершения потока производителя (первого звонящего).

 Jed Wesley-Smith13 нояб. 2012 г., 23:47
У него есть одна зависимость от Google 'с гуава - мы обнаружили, что каждый проект уже зависел от этого, и мы хотели использовать согласованный интерфейс Function. Недавно мы добавили Обещание, которое использует гуавуS ListenableFuture вещи, а также. Все остальные зависимости являются тестовыми (или необязательными в случае аннотаций find-bug).
 Jim13 нояб. 2012 г., 09:21
Это, кажется, делает то, что мне нужно, но, похоже, нужно много библиотек (Google и т. Д.)
 Jim13 нояб. 2012 г., 08:40
Это как нить локальная?
 Jim13 нояб. 2012 г., 08:51
Кажется, есть много зависимостей. Как я могу использовать это?
Решение Вопроса

ВашloadSomeHeavyData Метод может использовать блокирующий механизм, чтобы заставить все потоки дождаться завершения обновления, но позволить только одному из них действительно выполнить обновление:

private final AtomicBoolean updateStarted = new AtomicBoolean();
private final CountDownLatch updateFinished = new CountDownLatch(1);

public void loadSomeHeavyData() {
    if (updateStarted.compareAndSet(false, true)) {
        //do the loading
        updateFinished.countDown();
    } else {
        //update already running, wait
        updateFinished.await();
    }
}

Обратите внимание на мои предположения:

Вы хотите, чтобы все потоки ожидали завершения загрузки, чтобы они могли вызыватьdoSomeProcessing второй раз с обновленными даннымиты звонишь толькоloadSomeHeavyData один раз, никогда - если нет, вам нужно будет сбросить флаг и CountdownLatch (который тогда, вероятно, будет не самым подходящим механизмом).

РЕДАКТИРОВАТЬ

Ваш последний комментарий означает, что вы действительно хотите позвонитьloadSomeHeavyData более одного раза, просто не более одного разавовремя.

private final Semaphore updatePermit = new Semaphore(1);

public void loadSomeHeavyData() {
    if (updatePermit.tryAcquire()) {
        //do the loading and release updatePermit when done
        updatePermit.release();
    } else {
        //update already running, wait
        updatePermit.acquire();
        //release the permit immediately
        updatePermit.release();
    }
}
 assylias13 нояб. 2012 г., 08:47
@Jim Посмотрите мои изменения - вы должны обновить свой вопрос, чтобы быть более конкретным.
 Jim13 нояб. 2012 г., 15:24
Пожалуйста, проверьте также здесь:stackoverflow.com/questions/13362427/...
 Jim13 нояб. 2012 г., 14:34
Угловой случай, который вы упоминаете, например, 5 потоков получают исключениеодновременно и по какой-то причине из-за планирования поток-5 пытается получить семафор после перезагрузки данных (другие потоки уже закончили). Правильно? Так что в этом случае, как интервал поможет? Это мне не ясно. пожалуйста, объясни?
 Jim13 нояб. 2012 г., 13:25
Я понимаю, что вы имеете в виду. Есть ли способ избежать этого?
 assylias13 нояб. 2012 г., 13:52
@ Джим, один трюк - использовать атомарный булев и сбрасывать его в false через определенный период. И если это правда, пропустите встречу.
 assylias13 нояб. 2012 г., 10:16
Примечание: со вторым параметром может случиться, что один поток получит исключение, но к тому времени, когда он вызывает loadSomeHeavyData, данные фактически обновляются, и он загружает данные снова.
 Mingjiang Shi30 дек. 2015 г., 15:46
Для потока, который получает разрешение на загрузку данных, что, если он вызывает исключение при загрузке данных, приведет ли это к тому, что разрешение не будет выпущено? возможно используйте try finally, чтобы освободить разрешение в блоке finally.
 Jim13 нояб. 2012 г., 14:02
Но сейчас неt проблема зависит от временного интервала? Должно ли оно быть слишком маленьким? Слишком большим? Насколько большим?
 Jim13 нояб. 2012 г., 08:38
Ах, мне нужно будет позвонитьloadSomeHeacyData еще раз, если через некоторое время выдается исключение. Как я могу использовать ваш ответ в этом случае?
 assylias13 нояб. 2012 г., 14:27
@Jim Когда загрузка завершена - сколько вы хотите подождать, прежде чем будет выполнена другая загрузка?

С использованиемsynchronized ключевое слово:

public synchronized void someMethod(){  
    //doStuff
}

Вы гарантируете, что только один поток вступает за один раз.

Чтобы гарантировать, что метод вызывается только один раз, особой языковой функции нет; Вы можете создать статическую переменную типа boolean, которая устанавливается в true первым потоком, входящим в метод. При вызове метода всегда проверяйте этот флаг:

public class MyClass {
    private static boolean calledMyMethod;

    public synchronized void someMethod() {
        if(calledMyMethod) { 
            return;
        } else {
            calledMyMethod = true;
            //method logic
        }           
    }
} 
 Jim13 нояб. 2012 г., 08:22
К сожалению, я не могу изменить его, чтобы бытьsynchronized
public void someMethod()
{  
    //DO SOME STUFF
    try
    {  
        doSomeProcessing();  
    }   
    catch (Exception e)
    {   
        loadSomeHeavyData();  // Don't call here but add a request to call in a queue.
                              // OR update a counter
        doSomeProcessing();      
    }
}

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

Псевдокод может выглядеть так:

int noOfrequests = 0;
public void someMethod()
{
    // block incoming threads here.  
    while(isRefreshHappening);

    //DO SOME STUFF
    try
    { 
        doSomeProcessing();  
    }   
    catch (Exception e)
    {
        // Log the exception
        noOfrequests++;   
    }
}

// Will be run by only one thread
public void RefreshData()
{
    if(noOfrequests >= THRESHOLD)
    {
        isRefreshHappening = true;
        // WAIT if any thread is present in try block of somemethod
        // ...
        loadSomeHeavyData();
        noOfrequests = 0;
        isRefreshHappening = false;
    }
}
 Jim13 нояб. 2012 г., 08:31
Можно ли добавить небольшой фрагмент для вашего предложения?

вам нужно загружать данные в непредсказуемый, но ограниченный промежуток времени. Есть три возможных способа сделать это: 1) Вы можете окружить свой вызов loadSomeHeavyData оператором if, который контролирует доступ к методу. 2) Вы можете изменить свой метод для обработки потока управления (решение об обновлении или нет). 3) Написать поток обновления и позволить ему выполнить работу за вас. Первые две альтернативы могут использовать внешнее логическое значение или генерировать логическое решение, используя Timedelta между последним вызовом и текущим временем вызова. Третий вариант - это синхронизированный поток, который запускается каждые n секунд / минут и загружает тяжелые данные.

Ваш ответ на вопрос