A implementação atual do SecureRandomé thread-safe, especificamente os dois métodos mutantes nextBytes(bytes[])e setSeed(byte[])são sincronizados.
Bem, pelo que pude perceber, todos os métodos mutantes são eventualmente roteados por meio desses dois métodos e SecureRandomsubstituem alguns métodos Randompara garantir isso. Que funciona, mas pode ser frágil se a implementação for alterada no futuro.
A melhor solução é sincronizar manualmente na SecureRandominstância primeiro. Isso significa que cada pilha de chamadas adquirirá dois bloqueios no mesmo objeto, mas isso geralmente é muito barato em JVMs modernos. Ou seja, não há muito mal em se sincronizar explicitamente. Por exemplo:
SecureRandom rnd = ...;
byte[] b = new byte[NRANDOM_BYTES];
synchronized (rnd) {
rnd.nextBytes(b);
}