Самая большая проблема здесь заключается в том, что вы меняете, какой объект синхронизируется. То, что в итоге происходит, действительно недетерминировано.
В вашем синхронизированном блоке у вас есть:
synchronized (this.last) {
last.next = newLink;
last = newLink;
}
Поскольку последний изменяется, другой поток может ввести метод enque и одновременно войти в синхронизированный блок. Лучше всего иметь два объекта для блокировки:
private final Object ENQUEUE_LOCK = new Object();
private final Object DEQUEUE_LOCK = new Object();
Я не знаю, обязательно ли по этой причине ваши тесты не выполняются, но это решило бы проблему параллелизма.
Редактировать: Оказывается, по крайней мере для меня и моего тестирования наличие двух выделенных блокировок решает странные проблемы, которые я видел с очередью.