2017-01-18 2 views
0

Я наблюдал странное поведение приложения командной строки, которое вызывает SwingWorkers. Код не оптимален в том смысле, что он создает много пулов потоков. Однако из-за управления переменной generation все эти пулы, кроме последнего, не выполняют никакого кода. Это означает, что потоки из этих бассейнов не только не участвуют в гонках для замков, но также должны собираться и исчезать мусор.SwingWorker.process() не получает вызов в приложении командной строки

Минимальный рабочий пример (не делает ничего полезного) заключается в следующем:

package test; 

import java.util.List; 
import java.util.concurrent.ExecutorService; 
import java.util.concurrent.Executors; 
import javax.swing.SwingWorker; 

public class Tester { 

private final int threads; 
private ExecutorService threadPool; 
private final int size; 
private long currGen; 
private int left; 
private int done; 

public Tester(int size, int threads) { 
    this.threads = threads; 

    this.size = size; 

    this.currGen = 0; 

    this.left = size; 
    this.done = 0; 
} 

private class TestWorker extends SwingWorker<Object, Object> { 
    private final Tester tester; 
    private final long generation; 

    TestWorker(Tester tester, long generation) { 
     this.tester = tester; 
     this.generation = generation; 
    } 

    @Override 
    protected Object doInBackground() throws Exception { 
     while(this.tester.get(generation)) { 
      Thread.sleep(1); 
      publish(1); 
     } 

     return null; 
    } 

    @Override 
    protected void process(List<Object> results) { 
     for(Object n : results) { 
      this.tester.put(generation); 
     } 
    } 
} 

public void run() {  
    this.currGen++;   
    this.left = size; 
    this.done = 0; 

    System.out.printf("Starting %d\n", currGen); 

    this.threadPool = Executors.newFixedThreadPool(threads + 4); 

    for (int threadId = 0; threadId < threads; threadId++) { 
     threadPool.submit(new TestWorker(this, currGen)); 
    } 
} 

public synchronized boolean get(long generation) {   
    if (generation != currGen) { 
     return false; 
    } 

    if (this.left == 0) { 
     return false; 
    } 

    this.left--; 

    return true; 
} 

public synchronized void put(long generation) {   
    if (generation != currGen) { 
     return; 
    } 

    this.done++; 

    if (this.done == this.size) { 
     this.run(); 
    } 
} 
} 

Затем этот класс работает в главном методе моей программы по:

Tester tester = new Tester(30 * 400, 30); 

    tester.run(); 

Наблюдаемое поведение : Выход состоит из запуска 1 \ n [...] Начиная с 1034 \ n После этого процесс все еще жив, но больше строк не печатается. Количество потоков для моего процесса - 31014 в момент блокировки. Эксперимент проводили на машине с 24 сердечниками.

Ожидаемое поведение: Процесс должен поддерживать печать Запуск К \ п к = 1, 2, ... навсегда или бросить OutOfMemoryError вызванное слишком много ThreadPools созданных.

Представленный пример имеет ограниченную отладку. В какой-то момент у меня появилось больше команд printf, они подразумевали, что тупик возникает, когда все созданные потоки текущего поколения вызвали свои методы publish(), но метод process() не вызывается EDT.

+0

Что вы ожидаете? Когда 'this.done == this.size', вы вызываете' run() ', который устанавливает' done' в ноль. Сообщение печатается только в том случае, когда 'this.done> this.size', но этого достичь невозможно, когда вы устанавливаете' done' в ноль до достижения этого условия. Я не уверен, поняли ли вы цели пулов потоков, когда вы неоднократно создаете новые пулы ... Но цель этого странного кода в любом случае неясна. – Holger

+0

Вы печатаете сообщение «Starting ...» непосредственно перед созданием нового пула потоков, поэтому каждый раз, когда вы видите это сообщение, вы создаете новый пул потоков с фиксированным числом из 15 потоков. Поэтому, когда вы видите сообщение «Начиная с 2813», вы создали потоки 2813 × 15 == 42195. И вы думаете, что вы действительно можете исключить отношения между вашей программой, зависающими и создав потоки 42195, все из которых неоднократно вызывают методы «synchronized» на одном и том же объекте? – Holger

+1

Извините, если это звучит слишком жестко, но минимальное требование для вопроса - описать ожидаемое поведение, и невозможно угадать намерение вашей программы, поскольку оно просто не имеет никакого смысла. Все потоки изменяют одни и те же переменные, поэтому, если поток завершен или нет, он является чисто случайным. И так как каждое завершение запускает начало еще 15 потоков, вы просто создали вариант печально известной вилочной бомбы. – Holger

ответ

0

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

После того, как программа выполняется для K раундов (т.е. напечатанное Starting k\n), были K ThreadPools размера 34 созданных. Из них все потоки, кроме 30, выполняющие метод doInBackground() из последнего поколения, наконец, не выполняют никакого кода, но все равно running. 30 потоков, которые все еще выполняют код, синхронизируются по методам get() и put() и попадают в тупик, потому что поток AWT-eventdispatch по какой-либо причине не выполняет метод publish(). Этот тупик вызван единственным фактом, что существует много потоков, созданных и running (хотя большинство из них неактивны и не участвуют в гонке).

Консенсус после обсуждения кажется: из-за создания слишком большого количества потоков система (внешняя по отношению к Java) нарушает некоторые ограничения и делает JVM прекращением прогрессирования.

+0

Вы смотрите в неправильном направлении. Если EDT прекратил вызывать 'process()', он не может владеть блокировкой и не запускать больше потоков. Это подразумевает, что другие потоки должны продолжаться и, в конечном счете, переходить в состояние ожидания или даже прекращаться, как только некоторые из пулов будут возвращены сборщиком мусора (это может зависеть от версии Java, имеет ли пул такую ​​очистку). Возможно, EDT зависает, пытаясь создать больше потоков для нового пула. Это зависит от системы и выходит за рамки сборщика мусора. Я признаю, что я не пробовал вашу программу с 24 ядрами ... – Holger

+0

Я изменил код в первом сообщении, чтобы гарантировать, что потоки, созданные в последующих поколениях, не будут гоняться. Новый код работает для 1034 поколений и зашел в тупик. Вы видите какие-либо объяснения, почему, вместо каких-либо исключений, я получаю тупик? – mskrzypczak

+0

Вы по-прежнему создаете новые пулы потоков, даже если теперь вы сохраняете ссылку на него в переменную экземпляра. Либо используйте один и тот же пул для всех, например. 'if (this.threadPool == null) {this.threadPool = Executors.newFixedThreadPool (threads + 4); } 'или, если вы настаиваете на создании нового пула, вызывают остановку старого:' if (this.threadPool! = null) {this.threadPool.shutdown(); } this.threadPool = Executors.newFixedThreadPool (threads + 4); 'Обратите внимание, что' shutdown() 'не убивает потоки; они все равно выполнит свою задачу (ы) перед завершением. Это похоже на то, что вы хотите ... – Holger

Смежные вопросы