Em uma biblioteca em Java 7, tenho uma classe que fornece serviços para outras classes. Após criar uma instância dessa classe de serviço, um método pode ser chamado várias vezes (vamos chamá-lo de doWork()
método). Portanto, não sei quando o trabalho da classe de serviço está concluído.
O problema é que a classe de serviço usa objetos pesados e deve liberá-los. Defino essa parte em um método (vamos chamá-lo release()
), mas não é garantido que outros desenvolvedores usem esse método.
Existe uma maneira de forçar outros desenvolvedores a chamar esse método depois de concluir a tarefa da classe de serviço? Claro que posso documentar isso, mas quero forçá-los.
Nota: Não posso chamar o release()
método no doWork()
método, porque doWork()
precisa desses objetos quando for chamado a seguir.
AutoCloseable
foi feito para esse propósito exato.Respostas:
A solução pragmática é fazer a classe
AutoCloseable
e fornecer umfinalize()
método como um pano de fundo (se apropriado ... veja abaixo!). Então você conta com os usuários da sua classe para usar o try-with-resource ou ligarclose()
explicitamente.Infelizmente, não existe uma maneira 1 em Java para forçar o programador a fazer a coisa certa. O melhor que você pode esperar é escolher um uso incorreto em um analisador de código estático.
Sobre o tema dos finalizadores. Esse recurso Java possui muito poucos casos de uso bons . Se você contar com os finalizadores para arrumar, terá o problema de que pode levar muito tempo para que a arrumação aconteça. O finalizador será executado somente depois que o GC decidir que o objeto não está mais acessível. Isso pode não acontecer até que a JVM faça uma coleção completa .
Portanto, se o problema que você está tentando resolver é recuperar recursos que precisam ser liberados mais cedo , você perdeu o barco usando a finalização.
E caso você não tenha entendido o que estou dizendo acima ... quase nunca é apropriado usar finalizadores no código de produção, e você nunca deve confiar neles!
1 - Existem maneiras. Se você estiver preparado para "ocultar" os objetos de serviço do código do usuário ou controlar rigidamente seu ciclo de vida (por exemplo, https://softwareengineering.stackexchange.com/a/345100/172 ), o código do usuário não precisará ser chamado
release()
. No entanto, a API se torna mais complicada, restrita ... e IMO "feia". Além disso, isso não está forçando o programador a fazer a coisa certa . Está removendo a capacidade do programador de fazer a coisa errada!fonte
DefaultResource
eFinalizableResource
que estende a primeira e adiciona um finalizador. Na produção, você usa oDefaultResource
que não possui finalizador-> sem custos indiretos. Durante o desenvolvimento e o teste, você configura o aplicativo para usoFinalizableResource
que possui um finalizador e, portanto, adiciona sobrecarga. Durante o desenvolvimento e o teste, isso não é um problema, mas pode ajudar a identificar vazamentos e corrigi-los antes de atingir a produção. No entanto, se ocorrer um vazamento alcances PRD você pode configurar a fábrica para criar X%FinalizableResource
para id o vazamentoEsse parece ser o caso de uso da
AutoCloseable
interface e atry-with-resources
instrução introduzida no Java 7. Se você tiver algo comoseus consumidores podem usá-lo como
e não precisa se preocupar em chamar um explícito,
release
independentemente de seu código gerar uma exceção.Resposta adicional
Se o que você está realmente procurando é garantir que ninguém se esqueça de usar sua função, você deve fazer algumas coisas
MyService
sejam privados.MyService
garantir que ele seja limpo depois.Você faria algo como
Você o usaria como
Eu não recomendo esse caminho originalmente porque é hediondo sem lambdas Java-8 e interfaces funcionais (onde
MyServiceConsumer
ficaConsumer<MyService>
) e é bastante imponente para seus consumidores. Mas se o que você deseja apenas é querelease()
deve ser chamado, ele funcionará.fonte
try-with-resources
e não apenas omitamtry
, perdendo assim aclose
chamada?try-with-resources
?.close()
será chamado quando a classe for implementadaAutoCloseable
.using
bloco para finalização determinística? Pelo menos em C #, isso se torna um padrão bastante claro para os desenvolvedores adquirirem o hábito de usar ao aproveitar recursos que precisam de finalização determinística. Algo fácil e a formação de hábitos é a próxima melhor coisa quando você não pode forçar alguém a fazer alguma coisa. stackoverflow.com/a/2016311/84206sim você pode
Você pode absolutamente forçá-los a liberar e torná-lo 100% impossível de esquecer, impossibilitando a criação de novas
Service
instâncias.Se eles fizeram algo assim antes:
Em seguida, altere-o para que eles possam acessá-lo assim.
Ressalvas:
AutoCloseable
interface que alguém mencionou; mas o exemplo dado deve funcionar imediatamente e, exceto pela elegância (que é um objetivo legal por si só), não deve haver motivo principal para alterá-lo. Observe que isso pretende dizer que você poderia usarAutoCloseable
dentro de sua implementação privada; o benefício da minha solução em relação ao uso de seus usuáriosAutoCloseable
é que ela pode, novamente, não ser esquecida.new Service
chamada.Service
) pertence à mão do chamador ou não está fora do escopo desta resposta. Mas se você decidir que precisa absolutamente disso, é assim que pode fazê-lo.fonte
Você pode tentar usar o padrão de comando .
Isso fornece controle total sobre a aquisição e liberação de recursos. O chamador envia apenas tarefas ao seu Serviço, e você é responsável por adquirir e limpar recursos.
Há um problema, no entanto. O chamador ainda pode fazer isso:
Mas você pode resolver esse problema quando ele surgir. Quando houver problemas de desempenho e você descobrir que alguns chamadores fazem isso, simplesmente mostre a eles a maneira correta e resolva o problema:
Você pode tornar isso arbitrariamente complexo implementando promessas para tarefas que dependem de outras tarefas, mas liberar coisas também fica mais complicado. Este é apenas um ponto de partida.
Assíncrono
Como foi apontado nos comentários, o acima não funciona realmente com solicitações assíncronas. Está correto. Mas isso pode ser facilmente resolvido no Java 8 com o uso do CompleteableFuture , especialmente
CompleteableFuture#supplyAsync
para criar futuros individuais eCompleteableFuture#allOf
aperfeiçoar a liberação dos recursos quando todas as tarefas forem concluídas. Como alternativa, sempre é possível usar Threads ou ExecutorServices para lançar sua própria implementação de Futuros / Promessas.fonte
Se você puder, não permita que o usuário crie o recurso. Permita que o usuário passe um objeto de visitante para o seu método, o qual criará o recurso, passe para o método do objeto de visitante e libere depois.
fonte
AutoCloseable
faz uma coisa muito semelhante nos bastidores. Sob outro ângulo, é semelhante à injeção de dependência .Minha ideia foi semelhante à da Polygnome.
Você pode ter os métodos "do_something ()" em sua classe, apenas adicionando comandos a uma lista de comandos privada. Então, tenha um método "commit ()" que realmente faça o trabalho e chame "release ()".
Portanto, se o usuário nunca chamar commit (), o trabalho nunca será concluído. Se o fizerem, os recursos são liberados.
Você pode usá-lo como foo.doSomething.doSomethineElse (). Commit ();
fonte
Outra alternativa às mencionadas seria o uso de "referências inteligentes" para gerenciar seus recursos encapsulados de uma maneira que permita ao coletor de lixo limpar automaticamente sem liberar recursos manualmente. Sua utilidade depende, é claro, da sua implementação. Leia mais sobre este tópico nesta maravilhosa postagem no blog: https://community.oracle.com/blogs/enicholas/2006/05/04/understanding-weak-references
fonte
Para qualquer outra linguagem orientada a classe, eu diria que o colocaria no destruidor, mas como essa não é uma opção, acho que você pode substituir o método finalize. Dessa forma, quando a instância sair do escopo e for coletada como lixo, ela será liberada.
Não estou muito familiarizado com Java, mas acho que esse é o objetivo para o qual finalize () se destina.
http://docs.oracle.com/javase/7/docs/api/java/lang/Object.html#finalize ()
fonte
If you rely on finalizers to tidy up, you run into the problem that it can take a very long time for the tidy-up to happen. The finalizer will only be run after the GC decides that the object is no longer reachable. That may not happen until the JVM does a full collection.