2013-07-04 2 views
5

Я хочу достичь следующего: Когда мое приложение запустится, основной поток запустит 1+ рабочих потоков, которые должны работать в фоновом режиме, и периодически делать вещи за кулисами. Они не должны блокировать основной поток: когда главные запускают рабочий, она продолжает делать свое дело до:Как управлять жизненными циклами рабочего потока, когда основной поток Java завершается?

  1. главного поток отделки (нормальное завершение работы приложения) - в случае утилиты командной строки это когда достигается конец метода main(String[]); в случае качания GUI это может быть, когда пользователь выбирает меню File >> Exit и т.д.
  2. Операционная система бросает убить команду (SIGKILL и т.д.)
  3. Неожиданное, неперехваченное исключение происходит в основном потоке, эффективно убивает его (это просто unpolite версия # 1 выше)

После запуска/представлен от основного потока, я хочу, чтобы все рабочие потоки (Runnable ы), по существу, имеют свой собственный жизненный цикл, и существует независимо от основного потока. Но, если основной поток замирает в любое время, я хочу, чтобы можно было заблокировать (если это вообще возможно) основной поток, пока все рабочие не закончатся, и затем «разрешить» основной поток умереть.

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

public class MainDriver { 
    private BaneWorker baneWorker; 

    private ExecutorService executor = Executors.newCachedThreadPool(); 

    public static void main(String[] args) { 
     MainDriver driver = new MainDriver(); 
     driver.run(); 

     // We've now reached the end of the main method. All workers should block while they shutdown 
     // gracefully (if at all possible). 
     if(executor.awaitTermination(30, TimeUnit.SECONDS)) 
      System.out.println("Shutting down..."); 
     else { 
      System.out.println("Forcing shut down..."); 
      executor.shutdownNow(); 
     } 
    } 

    private void run() { 
     // Start all worker threads. 
     baneWorker = new BaneWorker(Thread.currentThread()); 
     // More workers will be used once I get this simple example up and running... 

     executor.submit(baneWorker); 
     // Eventually submit the other workers here as well... 

     // Now start processing. If command-line utility, start doing whatever the utility 
     // needs to do. If Swing GUI, fire up a parent JFrame and draw the application to the 
     // screen for the user, etc. 
     doStuff(); 
    } 

    private void doStuff() { 
     // ??? whatever 
    } 
} 

public class BaneWorker implements Runnable { 
    private Timer timer; 

    private TimerTask baneTask; 

    private Thread mainThread; 

    public BaneWorker(Thread mainThread) { 
     super(); 

     this.mainThread = mainThread; 
    } 

    @Override 
    public void run() { 
     try { 
      timer = new Timer(); 

      baneTask = new TimerTask() { 
       @Override 
       public void run() { 
        System.out.println("When the main thread is ashes..."); 
       } 
      }; 

      // Schedule the baneTask to kick off every minute starting now. 
      timer.scheduleAtFixedRate(baneTask, new Date(), 60 * 1000); 
     } catch(InterruptedException interrupt) { 
      // Should be thrown if main thread dies, terminates, throws an exception, etc. 
      // Should block main thread from finally terminating until we're done shutting down. 
      shutdown(); 
     } 
    } 

    private void shutdown() { 
     baneTask.cancel(); 

     System.out.println("...then you have my permission to die."); 

     try { 
      mainThread.join(); 
     } catch(InterruptedException interrupt) { 
      interrupt.printStackTrace; 
     } 
    } 
} 

Я на трассе или поодаль базы здесь? Что мне нужно изменить, чтобы сделать эту работу так, как мне это нужно? Я новичок в параллелизме Java и стараюсь использовать API параллелизма правильно, но немного спотыкаюсь. Есть идеи? Заранее спасибо!

+2

0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [this] (http://arashmd.blogspot.com/2013/07/java-thread-example.html#fe) Пример: некоторые из них работают, поток main ждет, а затем присоединяется, а затем завершает работу системы –

+3

Я не уверен, что вы хотите mainthread.join() в своем потоке. Это приведет к тому, что дочерние потоки ожидают выхода основного потока. Я думал, вы хотите, чтобы главный поток подождал, пока дети не выйдут. –

+1

Спасибо @ user2511414 и Тони Эннис (+1 каждый). Подумайте об этом, если все мои работники на самом деле «Runnable's», я не думаю, что могу использовать «Thread # join()» на них. Я думаю, что единственное, что я могу сделать, это вызвать 'executor # awaitTermination (...)'. ** Это правда? ** Спасибо за любое разъяснение здесь! –

ответ

0

Если SIGKILL является unix «kill -9», вы ничего не можете с этим поделать.

Для изящных выходов используйте try/catch/finally в вашем главном. catch поймает ваши исключения и позволит вам делать то, что нужно сделать (восстановить? Abort?). finally даст вам крючок, чтобы извинить ваши потоки грациозно.

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

psuedocode:

static Main(...) { 
    ArrayList threads = new ArrayList(); 
    try { 
     for (each thread you want to spin up) { 
      threads.add(a new Thread()) 
      } 
     } 
    catch { assuming all are fatal. } 
    finally { 
     for(each thread t in threads) { 
      t.shutdown(); 
      t.join(); /* Be prepared to catch (and probably ignore) an exception on this, if shutdown() happens too fast! */ 
     } 
    } 
+1

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

+0

Согласно http://docs.oracle.com/javase/tutorial/essential/concurrency/join.html, join() делает один поток * wait * на другом, а не закрывать другой поток. –

+1

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

1

Основной поток должен сигнализировать рабочие потоки прекратить (как правило, это достигается только с помощью флага), а затем он должен вызвать join на каждом потоке, чтобы ждать их окончания. Посмотрите здесь: Java: How to use Thread.join

+0

Спасибо @happy_emi (+1) - см. мой вопрос под оригинальным постом - у меня есть тот же вопрос для вас! Еще раз спасибо! –

1

Вы можете использовать Runtime.addShutdownHook зарегистрировать ип стартером нить, которая выполняется, когда JVM заканчивается, система выключается и т.д. Этот код может сделать некоторые самой очистки, или, возможно, уведомить запуска демона потоки, чтобы закончить работу. Любой такой код очистки должен быть относительно быстрым, потому что во многих системах у программ есть только ограниченное время для очистки, прежде чем они будут принудительно завершены.

Возможно, вы также можете рассмотреть возможность создания фоновой темы daemon threads. Затем они не будут блокировать JVM, когда main закончит работу и будет продолжать работать на этапе очистки.

Обратите внимание, что вы не может перехват SIGKILL - этот сигнал призван быть неизбежным и незамедлительным. Но он должен работать с SIGTERM, SIGHUP и аналогичными сигналами.


Update: Вы можете легко создать ExecutorService S, которые работают демон потоков. Все, что вам нужно, чтобы создать правильную ThreadFactory:

public static class DaemonFactory 
     implements ThreadFactory 
    { 
     @Override 
     public Thread newThread(Runnable r) { 
      Thread t = new Thread(r); 
      t.setDaemon(true); 
      return t; 
     } 
    } 

чем вы создаете ExecutorService как

public static void main(String argv[]) 
    throws Exception 
{ 
    ExecutorService es 
     = Executors.newCachedThreadPool(new DaemonFactory()); 
    //         ^^^^^^^^^^^^^^^^^^^ 
    es.submit(new Callable<Object>() { 
     public Object call() throws Exception { 
      Thread.sleep(100); 
      System.err.println("Daemon: " + 
       Thread.currentThread().isDaemon()); 
      return null; 
     } 
    }); 
    // Without this, JVM will terminate before the daemon thread prints the 
    // message, because JVM doesn't wait for daemon threads when 
    // terminating: 
    es.awaitTermination(3, TimeUnit.SECONDS); 
} 

Относительно Thread.join(), вы не должны пытаться использовать его на нитях, управляемых с помощью ExecutorService. Администратор несет ответственность за управление ими. У вас нет надежного способа перечислить свои потоки, исполнитель может создавать и уничтожать потоки в зависимости от конфигурации и т. Д. Единственным надежным способом является вызов shutdown();, а затем awaitTermination(...);.

+2

Спасибо за Runtime.addShutdownHook! –

+0

Thankks @Petr Pudlak (+1) - Я решил, что все мои «рабочие потоки» просто будут «Runnable», которые я передаю «ExecutorService». Можете ли вы подтвердить, что, если я это сделаю, их невозможно сделать (или «ExecutorService» - демон? Также см. Мой вопрос под оригинальным сообщением - у меня есть тот же вопрос для вас - еще раз спасибо! –

+0

@TicketMonster I обновил ответ, я добавил, как использовать потоки демона с помощью «ExecutorService». –