Finalmente consegui fazer funcionar mas com algumas modificações. A idéia é usar LockModeType.PESSIMISTIC_FORCE_INCREMENT em vez de PESSIMISTIC_WRITE. Usando este modo de bloqueio, os Cron Jobs se comportam da seguinte forma:
- Quando o primeiro job faz a seleção para atualização, tudo corre conforme o esperado, mas a versão do objeto muda.
- Se outro job tentar fazer a mesma seleção enquanto o primeiro ainda estiver em sua transação, o JPA lançará uma OptimisticLockException para que, se você capturar essa exceção, tenha certeza de que ela foi lançada para um bloqueio de leitura.
Esta solução tem várias contrapartes:
- SynchronizedCronJobTask deve ter um campo de versão e estar sob controle de versão com @Version
- Você precisa lidar com OptimisticLockException, e ela deve ser capturada fora do método de serviço transacional para fazer a reversão quando ocorrer o bloqueio.
- IMHO é uma solução não elegante, muito pior do que simplesmente um bloqueio onde os Cron Jobs esperam que os Jobs anteriores terminem.