Есть две основные проблемы, которые вам нужно решить. Первый заключается в следующем: как вы генерируете столкновение потоков? Во-вторых, как вы оцениваете свой тест?
Первый прост. Используйте большой молоток. Напишите некоторый код, способный обнаружить случай, когда один поток переходит в другой, и выполнить этот код примерно 50 раз в 50 последовательных потоках. Вы можете сделать это с помощью защелки обратного отсчета:
public void testForThreadClash() {
final CountDownLatch latch = new CountDownLatch(1);
for (int i=0; i<50; ++i) {
Runnable runner = new Runnable() {
public void run() {
try {
latch.await();
testMethod();
} catch (InterruptedException ie) { }
}
}
new Thread(runner, "TestThread"+i).start();
}
// all threads are waiting on the latch.
latch.countDown(); // release the latch
// all threads are now running concurrently.
}
Ваш метод testMethod () должен генерировать ситуацию, когда без синхронизированного блока какой-либо поток будет наступать на данные другого потока. Он должен быть в состоянии обнаружить этот случай и выдать исключение. (Приведенный выше код этого не делает. Исключение, скорее всего, будет сгенерировано в одном из тестовых потоков, и вам нужно включить механизм, который обнаружит его в основном потоке, но это отдельная проблема. Я оставил это для простоты, но вы можете сделать это с помощью второй защелки, которую тест может преждевременно очистить при исключении.)
Второй вопрос сложнее, но есть простое решение. Вопрос в следующем: откуда вы знаете, что ваш молоток вызовет столкновение? Конечно, ваш код имеет синхронизированные блоки, чтобы предотвратить конфликт, поэтому вы не можете ответить на вопрос. Но вы можете. Просто удалите синхронизированные ключевые слова. Поскольку именно синхронизированное ключевое слово делает ваш класс безопасным для потоков, вы можете просто удалить их и повторно запустить тест. Если тест действителен, теперь он не пройден.
Когда я впервые написал код выше, он оказался недействительным. Я никогда не видел столкновения. Так что это (пока) не действительный тест. Но теперь мы знаем, как получить четкий ответ на второй вопрос. Мы получаем неправильный ответ, но теперь мы можем поработать с тестом, чтобы сгенерировать искомую ошибку. Вот что я сделал: я только что выполнил тест 100 раз подряд.
for (int j=0; j<100; ++j) {
testForThreadClash();
}
Теперь мой тест надежно провалился примерно на 20-й итерации или около того. Это подтверждает, что мой тест действителен. Теперь я могу восстановить синхронизированное ключевое слово и повторно запустить тест, уверенный, что он скажет мне, является ли мой класс безопасным для потоков.