обзор
Thread jiggler — это простая инфраструктура тестирования кода для выявления проблем с многопоточностью. Он работает путем изменения байт-кода классов во время выполнения, чтобы вставить вызовы Thread.yield () между инструкциями — «перемешивая» потоки. Это значительно увеличивает вероятность обнаружения проблем с многопоточностью и делает это без необходимости изменения рабочего кода.
Фон
Недавно я исследовал, как тестировать многопоточный код на наличие потоков, и узнал об инструменте от IBM под названием ConTest , но не смог найти код, который мог бы использовать сам. Естественно, я думал, что у меня появится шип.
Рассмотрим этот канонический простой, но небезопасный для потока класс:
1
2
3
4
5
|
private int count = 0 ; public void count() { count++; } |
Байт-код метода подсчета:
1
2
3
4
5
|
DUP GETFIELD asm/Foo.counter : I ICONST_1 IADD PUTFIELD asm/Foo.counter : I |
Это обеспечивает несколько мест, где может быть переключение контекста, что означает, что счетчик может быть увеличен, но не сохранен, как ожидалось. Давайте рассмотрим быстрый юнит-тест:
01
02
03
04
05
06
07
08
09
10
11
12
13
|
Counter counter = new BadCounter(); int n = 1000 ; @Test public void singleThreadedTest() throws Exception { for ( int i = 0 ; i < n; i++) { counter.count(); } assertEquals(n, counter.getCount()); } ... |
Этот тест выполняется в одном потоке и проходит. Давайте попробуем запустить это в двух потоках и посмотрим, не получится ли это.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
public void threadedTest() throws Exception { final CompletionService<Void> service = new ExecutorCompletionService<Void>(Executors.newFixedThreadPool( 2 )); for ( int i = 0 ; i < n; i++) { service.submit( new Callable<Void>() { @Override public Void call() { counter.count(); return null ; } }); } for ( int i = 0 ; i < n; ++i) { service.take().get(); } assertEquals(n, counter.getCount()); } |
Это также проходит. На моем компьютере я могу увеличить n до 100 000, прежде чем он начнет последовательно отказывать.
1
2
|
Expected : 1000000 Actual : 999661 |
Только 0,04% тестов имели проблемы. Что мы узнали? Мы изучили простой способ запуска многопоточного теста, но мы узнали это, потому что мы не можем контролировать, когда потоки выполняют свою работу, это немного проб и ошибок.
Резьба
Поэтому одна из проблем, возникающих при использовании кода для поиска дефектов потоков, заключается в том, что вы не можете контролировать, когда потоки будут давать результаты. Тем не менее, мы можем переписать байт-код, чтобы вставить Thread.yield () в байт-код между инструкциями. В приведенном выше примере мы можем получить код для создания дополнительных проблем, изменив байт-код:
1
2
3
4
5
6
|
DUP GETFIELD asm/Foo.counter : I INVOKESTATIC java/lang/Thread.yield ()V ICONST_1 IADD PUTFIELD asm/Foo.counter : I |
Используя ASM, мы можем создать переписчик для вставки этих вызовов. JigglingClassLoader переписывает классы на лету, добавляя эти вызовы. Из этого мы можем создать бегущий JUnit для запуска, используя новый загрузчик классов для теста.
1
2
3
4
|
@Jiggle ( "threadjiggler.test.*" ) public class BadCounterTest { ... } |
Сейчас выполняется тест:
1
2
|
Expected : 1000000 Actual : 836403 |
Количество тестов, в которых мы видим проблему с многопоточностью, увеличилось до 16%. Мы сделали это без перекомпиляции кода или влияния на другие модульные тесты, работающие в той же JVM.
Упражнение для читателя
SimpleDateFormat — это хорошо известный, не потокобезопасный класс в Java. Напишите тест, который покачивает класс. Почему это не потокобезопасно? Как бы вы переписали его так, чтобы он был потокобезопасным? Как вы можете сделать это без использования ThreadLocal, блокировки или синхронизации?
Исходный код
Код для этого можно найти на Github .
Дальнейшее чтение
Я написал пост о тестировании многопоточного кода на корректность . Вы также можете прочитать более широко:
- Параллельные шаблоны ошибок и методы их тестирования — Эйтан Фарчи, Ярден Нир, Шмуэль Ур Исследовательские лаборатории IBM в Хайфе
- Презентация, описывающая сложность тестирования и отладки параллельного программного обеспечения — Шмуэль Ур
- Теория и практика Java: характеристика безопасности потоков