Содержание

вторник, 5 апреля 2011 г.

Ох уж эти фреймворки, облегачающие жизнь ленивому программисту

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

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

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

Раз приложение написано на Swing, то как минимум там есть еще один поток Event Disparcher Thread (EDT). Как вы знаете, это поток, в котором должны прорисовываться все пользовательские компоненты. Разработчики приложения, которое я исследовал слава богу об этом знали, но как выяснилось позже, знали они, к сожалению, намного больше.

На этом этапе исследования я имел следующую картину: есть поток, который в цикле обрабатывает данные и для отображения передает их в EDT. Но EDT же один-единственный поток, в который данные на прорисовку достоверно поступают последовательно, так как же тогда получается, что они могут показываться в обратном порядке? Может быть EDT в какой-то момент умирает и рождается новый, таким образом мы имеем больше одного потока? Но никаких эксепшенов я не видел, да и логирование текущего потока (System.out.println(Thread.currentThread())) показало, что оба действия происходят в одном и том же потоке. Запустив приложение под дебагом, я опять увидел, что в кусок кода выполняемый в EDT мы заходим повторно, не выйдя из него в предыдущий раз. Тут у меня вообще голова кругом пошла. Мне показалось, что я сошел сума и потоки это что-то такое несусветное, о чем я понятия не имею.
Приведу кусок программы, которую я исследовал в псевдокоде.
где, helper.getValue - содержит обычный код, без какой-либо магии.

Ну что, дорогой читатель, есть какие-нибудь идеи?

Наконец-то я сообразил снять thread dump, и тут все встало на свои места. Оказывается в нашем приложении используется "замечательная" библиотека Spin.off, которая как раз казалось бы делает очень полезную вещь. Так как EDT один на все приложение, то трудоемкие операции имеет смысл выполнять в фоновом процессе. Spin.off оборачивает класс и возвращает для него заглушку (helper в псевдокоде выше), где каждый метод обернут специальным кодом, который проверяет текущий поток, и если это EDT, то вызванный метод запускается в фоновом потоке, а в очередь EDT вталкивается следующее событие. Результат фонового процесса будет обработан только тогда, когда в EDT полностью обработается запущенное вне очереди событие. Вот такой вот неявный реордеринг операций получился.


Постойте, я же выше писал, что helper.getValue содержит обычный код, без какой-либо магии. Все верно, дело в том, что мы используем Spring, и создание объекта helper находится далеко за пределами приведенного участка кода и просто инжектится в данную область. С помощью IDE я проверил, что у интефейса есть всего одна реализация, которую и посмотрел, чтобы убедится, что там никакой магии не происходит.

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