Acho que ninguém realmente respondeu à pergunta , então vou tentar.
O volátil e o primeiro if (instance == null)
não são "necessários". O bloqueio tornará este código seguro para threads.
Portanto, a questão é: por que você adicionaria o primeiro if (instance == null)
?
A razão é presumivelmente para evitar a execução desnecessária da seção bloqueada do código. Enquanto você está executando o código dentro do bloqueio, qualquer outro thread que tente também executar esse código é bloqueado, o que tornará seu programa lento se você tentar acessar o singleton com freqüência de muitos threads. Dependendo do idioma / plataforma, também pode haver sobrecargas do próprio bloqueio que você deseja evitar.
Portanto, a primeira verificação de nulo é adicionada como uma maneira realmente rápida de ver se você precisa do bloqueio. Se você não precisa criar o singleton, pode evitar o bloqueio totalmente.
Mas você não pode verificar se a referência é nula sem bloqueá-la de alguma forma, porque devido ao cache do processador, outra thread poderia alterá-la e você leria um valor "obsoleto" que levaria você a inserir o bloqueio desnecessariamente. Mas você está tentando evitar um bloqueio!
Portanto, você torna o singleton volátil para garantir a leitura do valor mais recente, sem a necessidade de usar um bloqueio.
Você ainda precisa do bloqueio interno porque o volátil o protege apenas durante um único acesso à variável - você não pode testá-lo e configurá-lo com segurança sem usar um bloqueio.
Agora, isso é realmente útil?
Bem, eu diria "na maioria dos casos, não".
Se Singleton.Instance pode causar ineficiência devido aos bloqueios, por que você está ligando para ele com tanta frequência que isso seria um problema significativo ? O ponto principal de um singleton é que há apenas um, então seu código pode ler e armazenar em cache a referência do singleton uma vez.
O único caso em que consigo pensar em que esse armazenamento em cache não seria possível seria quando você tivesse um grande número de threads (por exemplo, um servidor usando um novo thread para processar cada solicitação poderia estar criando milhões de threads de execução muito curta, cada um que teria que chamar Singleton.Instance uma vez).
Então, eu suspeito que o bloqueio de dupla verificação é um mecanismo que tem um lugar real em casos muito específicos de desempenho crítico, e então todo mundo subiu no movimento "esta é a maneira correta de fazer isso" sem realmente pensar o que ele faz e se será realmente necessário caso eles o estejam usando.