Estou usando o AdoptOpenJDK jdk81212-b04
no Ubuntu Linux, executando o Eclipse 4.13. Eu tenho um método no Swing que cria uma lambda dentro de uma lambda; ambos provavelmente são chamados em threads separados. É assim (pseudocódigo):
private SwingAction createAction(final Data payload) {
System.out.println(System.identityHashCode(payload));
return new SwingAction(() -> {
System.out.println(System.identityHashCode(payload));
//do stuff
//show an "are you sure?" modal dialog and get a response
//show a file selection dialog
//when the dialog completes, create a worker and show a status:
showStatusDialogWithWorker(() -> new SwingWorker() {
protected String doInBackground() {
save(payload);
}
});
Você pode ver que as lambdas têm várias camadas de profundidade e, eventualmente, a "carga útil" capturada é salva em um arquivo, mais ou menos.
Mas antes de considerar as camadas e os threads, vamos diretamente ao problema:
- Na primeira vez que ligo
createAction()
, os doisSystem.out.println()
métodos imprimem exatamente o mesmo código hash, indicando que a carga capturada dentro do lambda é a mesma para a qual passeicreateAction()
. - Se eu ligar mais tarde
createAction()
com uma carga útil diferente, os doisSystem.out.println()
valores impressos serão diferentes! Em particular, a segunda linha impressa sempre indica o mesmo valor que foi impresso na etapa 1 !!
Eu posso repetir isso repetidamente; a carga útil transmitida continuará recebendo um código hash de identidade diferente, enquanto a segunda linha impressa (dentro do lambda) permanecerá a mesma! Eventualmente, algo clicará e, de repente, os números serão os mesmos novamente, mas eles divergirão com o mesmo comportamento.
De alguma forma, o Java está armazenando em cache o lambda, bem como o argumento que está indo para o lambda? Mas como isso é possível? O payload
argumento é marcado final
e, além disso, as capturas de lambda precisam ser efetivamente finais de qualquer maneira!
- Existe um bug do Java 8 que não reconhece um lambda não deve ser armazenado em cache se a variável capturada tiver vários lambdas de profundidade?
- Existe um bug do Java 8 que está armazenando em cache argumentos lambdas e lambda entre threads?
- Ou existe algo que eu não entendo sobre argumentos do método de captura lambda versus variáveis locais do método?
Primeira falha na tentativa de solução alternativa
Ontem, pensei em impedir esse comportamento simplesmente capturando o parâmetro method localmente na pilha de métodos:
private SwingAction createAction(final Data payload) {
final Data theRealPayload = payload;
System.out.println(System.identityHashCode(theRealPayload));
return new SwingAction(() -> {
System.out.println(System.identityHashCode(theRealPayload));
//do stuff
//show an "are you sure?" modal dialog and get a response
//show a file selection dialog
//when the dialog completes, create a worker and show a status:
showStatusDialogWithWorker(() -> new SwingWorker() {
protected String doInBackground() {
save(theRealPayload);
}
});
Com essa linha única Data theRealPayload = payload
, se, a partir de agora, eu usei em theRealPayload
vez de payload
repentinamente o bug não aparecer mais, e toda vez que liguei createAction()
, as duas linhas impressas indicariam exatamente a mesma instância da variável capturada.
No entanto, hoje essa solução alternativa parou de funcionar.
Problema separado de endereços de correção de bug; Mas por que?
Eu encontrei um bug separado que estava lançando uma exceção dentro showStatusDialogWithWorker()
. Basicamente, showStatusDialogWithWorker()
é suposto criar o trabalhador (no lambda passado) e mostrar uma caixa de diálogo de status até que o trabalhador seja concluído. Houve um erro que criaria o trabalhador corretamente, mas não conseguiu criar a caixa de diálogo, lançando uma exceção que surgiria e nunca seria pego. Corrigi esse bug para que o showStatusDialogWithWorker()
diálogo seja exibido com êxito quando o trabalhador estiver em execução e depois o feche após a conclusão do trabalhador. Agora não posso mais reproduzir o problema de captura lambda.
Mas por que algo interno se showStatusDialogWithWorker()
relaciona com o problema? Quando eu estava imprimindo System.identityHashCode()
fora e dentro do lambda, e os valores eram diferentes, isso estava acontecendo antes de showStatusDialogWithWorker()
ser chamado e antes da exceção ser lançada. Por que uma exceção posterior fará diferença?
Além disso, permanece a questão fundamental: como é possível que um final
parâmetro passado por um método e capturado por um lambda possa mudar?
final
argumento de método pode mudar fora e dentro do lambda? E por que capturar a variável como final
variável local na pilha faria alguma diferença?
final
variável na pilha não resolve mais o problema. Eu continuo investigando.
SwingAction
própria. O que você faz com os SwingAction
retornados do método?