Когда поток выходит за рамки?

Я написал программу, которая считает строки, слова и символы в тексте: она делает это с помощью потоков. Иногда он отлично работает, но иногда не так хорошо. В конечном итоге происходит то, что переменные, указывающие на количество подсчитанных слов и символов, иногда оказываются недостаточными, а иногда - нет.

Мне кажется, что нити иногда заканчиваются прежде, чем они могут сосчитать все слова или символы, которые они хотят. Это потому, что эти потоки выходят из области видимости при разрыве цикла while (true)?

Я включил код из нитевой части моей проблемы ниже:

private void countText() {
  try {
    reader = new BufferedReader(new FileReader("this.txt"));
    while (true) {
      final String line = reader.readLine();
      if (line == null) {break;}
      lines++;
      new Thread(new Runnable() {public void run() {chars += characterCounter(line);}}).start();
      new Thread(new Runnable() {public void run() {words += wordCounter(line);}}).start();
      println(line);
    }

  } catch(IOException ex) {return;}

}

(Дополнительный вопрос: я впервые о чем-то спросил и опубликовал код. Я не хочу использовать StackOverflow вместо Google и Википедии, и меня беспокоит, что это неподходящий вопрос? Я попытался задать вопрос более общий, так что я не просто прошу помощи с моим кодом ... но есть ли другой веб-сайт, на котором этот вопрос может быть более уместным?)

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
9
0
3 356
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Звучит как хороший вопрос для меня ... Я думаю, проблема может быть связана с атомарностью символов + = и слов + = - несколько потоков могут вызывать это одновременно - делаете ли вы что-нибудь, чтобы убедиться, что существует без чередования.

То есть:

Поток 1, имеет символы = 10, хочет добавить 5

Поток 2, имеет символы = 10, хочет добавить 3

Поток 1 отрабатывает новый итог, 15

Поток 2 показывает новый итог, 13

Поток 1 устанавливает символы в 15

Поток 2 устанавливает символы в 13.

Возможно, это возможно, если вы не используете synchronized при обновлении этих vars.

Ага! Видите ли, я полностью узнал о чередовании и атомарности, а также о синхронизации и блокировках, но мне это никогда бы не пришло в голову. Несомненно, в этом и заключается проблема!

Ziggy 14.11.2008 14:23

Хм ... Я использовал synchronized (this) {вокруг + = stuff}, но все равно получаю непредсказуемые результаты ...

Ziggy 14.11.2008 14:26

о, чувак, я не думаю, что это все. Я добавил println (Thread.activeCount ()); это дало бы мне представление о том, что происходит. Кажется, что я только иногда получаю полные 12 потоков активными до завершения цикла while. В том-то и проблема: не хватает времени!

Ziggy 14.11.2008 14:35

Я предполагаю, что BufferedReader загружает весь файл в память, если это небольшой файл, поэтому фактический ввод-вывод чтения через файл будет очень быстрым. Таким образом, вы мгновенно проходите цикл while, и ваши потоки все еще запускаются к тому времени, когда вы выходите из цикла.

Sam Stokes 14.11.2008 14:58

Как уже правильно указал Крис Кимптон, у вас проблема с обновлением chars и words в разных потоках. Синхронизация на this также не будет работать, потому что this является ссылкой на текущий поток, что означает, что разные потоки будут синхронизироваться на разных объектах. Вы можете использовать дополнительный «объект блокировки», который вы можете синхронизировать, но самый простой способ исправить это, вероятно, будет использовать AtomicIntegers для двух счетчиков:

AtomicInteger chars = new AtomicInteger();
...
new Thread(new Runnable() {public void run() { chars.addAndGet(characterCounter(line));}}).start();
...

Хотя это, вероятно, решит вашу проблему, Более подробный ответ Сэма Стоука полностью прав, исходная конструкция очень неэффективна.

Чтобы ответить на ваш вопрос о том, когда поток «выходит из области видимости»: вы запускаете два новых потока для каждой строки в вашем файле, и все они будут выполняться, пока не достигнут конца своего метода run(). Это если вы не сделаете их потоки демона), в этом случае они выйдут, как только потоки демонов будут единственными, которые все еще работают в этой JVM.

Я реализовал AtomicIntegers, и это увеличило мой успех. Есть еще серии, в которых оба счета ниже, чем должны быть ...

Ziggy 14.11.2008 14:48

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

Sam Stokes 14.11.2008 14:49
Ответ принят как подходящий

Другой резьбовой дизайн упростил бы поиск и устранение такого рода проблем и был бы более эффективным в сделке. Это длинный ответ, но его краткое содержание таково: «если вы выполняете потоки на Java, проверьте java.util.concurrent как можно скорее)».

Я предполагаю, что вы используете этот код в многопоточности для изучения потоков, а не для ускорения подсчета слов, но это очень неэффективный способ использования потоков. Вы создаете два потока на строку - две тысячи потоков для файла из тысячи строк. Создание потока (в современных JVM) использует ресурсы операционной системы и, как правило, довольно дорого. Когда два - не говоря уже о двух тысячах - потоков должны получить доступ к общему ресурсу (например, вашим счетчикам chars и words), возникающая конкуренция за память также снижает производительность.

Создание переменных счетчика synchronized как Крис Кимптон предлагает или Atomic как WMR предлагает, вероятно, исправит код, но это также значительно ухудшит эффект конкуренции. Я почти уверен, что он будет работать медленнее, чем однопоточный алгоритм.

Я предлагаю иметь только один долговечный поток, который следит за chars, и один для words, каждый с рабочей очередью, в которую вы отправляете задания каждый раз, когда хотите добавить новый номер. Таким образом, только один поток записывает в каждую переменную, и если вы внесете изменения в дизайн, будет более очевидно, кто за что отвечает. Это также будет быстрее, потому что не будет конкуренции за память и вы не создадите сотни потоков в замкнутом цикле.

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

Java (начиная с версии 1.5 и выше) упрощает реализацию такого дизайна: ознакомьтесь с java.util.concurrent.Executors.newSingleThreadExecutor. Это также упрощает добавление большего количества параллелизма позже (при условии правильной блокировки и т. д.), Поскольку вы можете просто переключиться на пул потоков, а не на один поток.

Я не ждал завершения цепочек. Вы правы, я просто делаю это, чтобы получить представление о методах, которые я буду использовать с потоками: назначение вообще не требовало потоков. Как вы ждете завершения потока? Могу я просто подождать, пока Thread.activeCount () вернет небольшое число?

Ziggy 14.11.2008 14:54

Thread.join () ожидает смерти отдельного потока. Ожидание, когда счетчик потоков станет равным 1, может сработать - я подозреваю, что вы можете столкнуться с условиями гонки с потоками, которые находятся в процессе запуска, но я не уверен.

Sam Stokes 14.11.2008 15:02

Если вы хотите разобраться с потоками, я рекомендую изучить способ выполнения действий Executor / thread pool / work queue. Как только вы осознаете это, на самом деле гораздо проще рассуждать, чем создавать потоки вручную.

Sam Stokes 14.11.2008 15:04

В ПОРЯДКЕ! Я посмотрю, прежде чем попробовать еще раз (этот отсчет до 1 сработал. На самом деле отсчет до 4 сработал ... по какой-то причине! В любом случае, это сработало, перейдем к следующей проблеме ... программе!).

Ziggy 14.11.2008 15:09

Другие вопросы по теме