Как запустить поток, только если он не выполняется или не запущен ранее, или должен быть создан только один экземпляр потока

#java #multithreading #thread-safety

#java #многопоточность #безопасность потоков

Вопрос:

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

     private void scheduleMessages() {
    new Thread(new Runnable() {
        @Override
        public void run() {
         //Some operations
        }
    }).start();
}
  

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

Комментарии:

1. Возможно, заблокируйте эту кнопку, пока поток не завершит свою работу (или не будет прерван)

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

3. Но хотите ли вы выполнять каждый «щелчок» или кнопка «отключена» во время выполнения? Если вам нужно запускать каждый щелчок, используйте фиксированный пул потоков и отправляйте в него каждый щелчок (с одним потоком). Если нет, идея флага Лукаса работает нормально, но я бы попытался переписать это, чтобы быть потокобезопасным, два потока могут быть выполнены, если scheduleMessages метод вызывается дважды перед запуском первого потока.

Ответ №1:

если вы не можете создать экземпляр этого для проверки isActive() , вам следует создать переменную семафора — логическое значение, которое вы устанавливаете true при запуске потока и устанавливаете false , когда вы закончите.

 private void scheduleMessages() {
    if (!taskRunning){
        new Thread(new Runnable() {
            @Override
            public void run() {
               taskRunning = true;
               //Some operations
               taskRunning = false;
            }
        }).start();
    }
}
  

Комментарии:

1. К вашему сведению: это небезопасно для потоков, вы можете просто синхронизировать метод и установить taskRunning = true вне Runnable .

Ответ №2:

Пусть этот поток будет фоновым потоком — возможно, инициализируйте его при первом нажатии кнопки.

Пусть этот поток прослушивает очередь и обрабатывает сообщения в этой очереди.

Всякий раз, когда кнопка нажимается снова, помещайте новое сообщение в очередь.

Ответ №3:

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

 private ExecutorService services;
private final static int POOL_SIZE = 1;

public MessagesService(){
    services = Executors.newFixedThreadPool(POOL_SIZE);
}

public void scheduleMessages(Runnable r){
    services.submit(r);
}
  

Если вы вызываете addCall x раз, x поток будет выполнен в конце, но никогда не будет использовать больше, чем количество потоков, доступных в пуле. Здесь 1 поток.


Для системы, которая принимает только один запрос, вы можете использовать тот же подход, но проверить Future возвращенный одним исполнителем потока. Таким образом, вы можете проверить состояние службы.

 private ExecutorService services;
private Future<?> lastCall; 

public MessagesService() {
    services = Executors.newSingleThreadExecutor();
    lastCall = null;
}

public synchronized void scheduleMessages(Runnable r) {
    if(!isScheduled()){
        lastCall = services.submit(r);
    }
}

public boolean isScheduled(){
    return lastCall != null amp;amp; !lastCall.isDone();
}
  

Таким образом, Runnable не нужно обновлять флаг, который дает повторно используемое решение.

Вот пример Runnable для тестирования этих кодов :

 new Runnable() { 
    System.out.println("Running");
    try {
       Thread.sleep(500);
    } catch (Exception e) {
        e.printStackTrace();
    }
}