2017-02-05 14 views
6

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

  • Этот метод должен быть выполнен только один раз. И как только он будет выполнен, он не может быть выполнен повторно, поэтому, если кто-то попытается выполнить его снова, он должен вернуться обратно, записав какое-то полезное сообщение об ошибке already executed или что-нибудь полезное.
  • И он должен выполняться только с одним потоком. Поэтому, если несколько потоков вызывают метод ниже, тогда он должен вызываться только одним потоком, а другие потоки должны ждать завершения инициализации?

Ниже мой метод:

public void initialize() { 
    List<Metadata> metadata = getMetadata(true); 
    List<Process> process = getProcess(); 
    if (!metadata.isEmpty() && !process.isEmpty()) { 
     Manager.setAllMetadata(metadata, process); 
    } 
    startBackgroundThread(); 
    } 

Возможно ли это сделать? Я работаю с Java 7.

+0

Если вы хотите, чтобы убедиться, что часть кода выполняется ровно один раз, чем я думаю, что положить его в классе статический инициализатор перечислимого типа является так близко, как вы можете получить. JVM гарантирует, что это будет вызываться не более одного раза за загрузчик классов. Но лучше этого, я не думаю, что есть ясный способ дать такую ​​гарантию. Может быть, если бы вы рассказали больше о вашем прецеденте? – korolar

+0

У меня есть этот метод в одном из моих классов, который инициализирует все наши метаданные и как только инициализация завершена, то только я хочу двигаться вперед в своем приложении. – user1950349

+1

Не помещал бы его в статический инициализатор этого класса? – korolar

ответ

6

@ Решение ShayHaned использует блокировку. Вы можете сделать его более эффективным с помощью AtomicBoolean как:

AtomicBoolean wasRun = new AtomicBoolean(false); 
CountDownLatch initCompleteLatch = new CountDownLatch(1); 

public void initialize() { 
    if (!wasRun.getAndSet(true)) { 
     List<Metadata> metadata = getMetadata(true); 
     List<Process> process = getProcess(); 
     if (!metadata.isEmpty() && !process.isEmpty()) { 
      Manager.setAllMetadata(metadata, process); 
     } 
     startBackgroundThread(); 
     initCompleteLatch.countDown(); 
    } else { 
     log.info("Waiting to ensure initialize is done."); 
     initCompleteLatch.await(); 
     log.warn("I was already run"); 
    } 
} 

выше предполагает, что вы не должны ждать работы в startBackgroundThread завершения. Если да, то решение становится:

AtomicBoolean wasRun = new AtomicBoolean(false); 
CountDownLatch initCompleteLatch = new CountDownLatch(1); 

public void initialize() { 
    if (!wasRun.getAndSet(true)) { 
     List<Metadata> metadata = getMetadata(true); 
     List<Process> process = getProcess(); 
     if (!metadata.isEmpty() && !process.isEmpty()) { 
      Manager.setAllMetadata(metadata, process); 
     } 
     // Pass the latch to startBackgroundThread so it can 
     // call countDown on it when it's done. 
     startBackgroundThread(initCompleteLatch); 
    } else { 
     log.info("Waiting to ensure initialize is done."); 
     initCompleteLatch.await(); 
     log.warn("I was already run"); 
    } 
} 

Причиной этого является то, что работает AtomicBoolean.getAndSet(true) будет, в одной атомарной операции, возвращает значение, которое было ранее установленное для и сделать новое значение будет true. Таким образом, первый поток для получения вашего метода получит возвращаемое false (поскольку переменная была инициализирована как false), и она будет атомарно установить ее в true. Поскольку этот первый поток получил false, он примет первую ветвь в операторе if, и ваша инициализация произойдет. Любые другие вызовы обнаружат, что wasRun.getAndSet возвращает true, так как первый поток установил его в true, чтобы они заняли 2-ю ветку, и вы получите только сообщение журнала.

CountDownLatch инициализирован до 1, так что все потоки, отличные от первого вызова await на нем. Они будут блокироваться до тех пор, пока первый поток не вызовет countDown, который установит счетчик на 0, освободив все ожидающие потоки.

+0

Можете ли вы добавить какое-то объяснение, чтобы я мог понять? Также он позаботится о моих условиях? Если вы сможете объяснить это, тогда это поможет мне понять. Я занимался некоторыми исследованиями, и я подумал, что мне, возможно, придется использовать 'CountDownLatch' вместе с' AtomicBoolean', как вы предложили здесь? – user1950349

+0

Добавлено объяснение. –

+1

Я не думаю, что этот ответ полностью удовлетворяет требованиям. @ user1950349 второе требование не выполняется. Здесь не все потоки после первого будут считать этот метод _was_ run; но метод может быть запущен. Все потоки после первого должны подождать, а это значит, что функция 'CountdownLatch' будет эффективной. Этот [post] (http://stackoverflow.com/questions/289434/how-to-make-a-java-thread-wait-for-another-threads-output) охватывает этот подход - в частности, ответ @ pdeva. – Keith

1

• И он должен выполняться только с одним потоком. Поэтому, если несколько потоков вызывают метод ниже, тогда он должен вызываться только одним потоком, а другие потоки должны ждать завершения инициализации?

public static final Object singleThreadLock = new Object(); 

public void initialize() 
{ 
    synchronized(singleThreadLock) 
    { 

     List<Metadata> metadata = getMetadata(true); 
     List<Process> process = getProcess(); 
     if (!metadata.isEmpty() && !process.isEmpty()) 
     { 
      Manager.setAllMetadata(metadata, process); 
     } 
     startBackgroundThread(); 
    } 
    } 

Эти строки кода гарантии, что Initialize() будет вызываться только один раз для каждого потока, а так singleThreadLock объявлен статическим, то ваш в настоящее время порождал JVM не только не позволит какой-либо другой поток, чтобы получить доступ к замку, пока полностью синхронизированный блок полностью выполнен. Также избегайте попыток синхронизации (это), поскольку такие заявления могут привести к серьезным проблемам параллелизма.

+0

Ничто не останавливает несколько потоков от _eventually_, вызывающих эту функцию. Прочтите его второе требование. Некоторая переменная 'wasRun' должна быть установлена ​​в значение true после вызова' startBackgroundThread() ', а затем' wasRun' должна быть оценена в начале критического раздела. – Keith

2

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

static boolen flag; 
public void initialize() { 
if (flag) 
{// return from here or some message you want to generate 
}else{ 
    List<Metadata> metadata = getMetadata(true); 
    List<Process> process = getProcess(); 
    if (!metadata.isEmpty() && !process.isEmpty()) { 
     Manager.setAllMetadata(metadata, process); 
    } 
     flag = true; 
    startBackgroundThread();  }} 

Я надеюсь, что это решает ваш запрос