Temos uma tarefa a cumprir dummyMethod
.
private synchronized void dummyMethod(){
Log.d("debug", "do nothing in dummyMethod()")
}
Executamos essa tarefa várias vezes.
private synchronized void fooMethod() throws InterruptedException{
for (int i = 0; i < 10; i++){
dummyMethod();
}
Thread.sleep(10000)
Log.d("debug", "fooMethod() finished")
}
Com o código acima, o sincronizado dummyMethod()
é executado 10 vezes imediatamente.
Porém, com o código a seguir, dummyMethod()
é chamado apenas uma vez imediatamente, e somente depois fooMethod()
de concluído é que é finalizado e executado mais 9 vezes.
private synchronized void fooMethod() throws InterruptedException{
new Thread(()->{
for (int i = 0; i < 10; i++){
dummyMethod();
}
}).start();
Thread.sleep(10000)
Log.d("debug", "fooMethod() finished")
}
Neste caso, o logcat mostra:
Long monitor contention with owner main (18077) at void ...fooMethod()(MainActivity.java:1106) waiters=0 in void ...MainActivity.dummyMethod() for 10.001s
Achei que não iria bloquear com um novo thread no último caso. Alguém poderia lançar alguma luz sobre isso? Como um método sincronizado pode executar outro método sincronizado várias vezes de forma assíncrona em um novo thread?
Do meu comentário -
Deste artigo Baeldung : todos os blocos sincronizados do mesmo objeto podem ter apenas um thread executando-os ao mesmo tempo
Portanto, o thread atual já está em um método sincronizado e o novo thread não pode ser executado imediatamente,
dummyMethod()
pois é o mesmo objeto.Uma ligeira modificação no código original, mostrando o uso de uma nova instância permite que o novo Thread seja executado imediatamente
dummyMethod()
:[Advertência: não sou especialista em simultaneidade Java.]
Fiz uma versão completa e independente do seu código.
Este código é usado
CopyOnWriteArrayList
como um log seguro para threads.Quando executado:
Examine os carimbos de data/hora. Observe que após o início da demonstração, nada é registrado por dez segundos completos. Por que esperar? Temos que analisar alguns fatos.
Esteja ciente de que nossos dois métodos,
dummyMethod
&fooMethodThreaded
, são métodos de instância na mesma classe,App
e ambos são declarados comosynchronized
. Os métodos sincronizados na mesma classe serão sincronizados em toda a instância, não em cada método separadamente. Portanto, métodos sincronizados no mesmo objeto bloquearão uns aos outros; apenas um pode correr por vez. Consulte Bloqueio de método sincronizado Java no objeto ou método? .Adicione o fato observado no comentário de Andrew S :
Você tem uma instância de
App
execução no thread principal. Lá executamos ofooMethodThreaded
método. Essesynchronized
método bloqueia nossa únicaApp
instância. Em seguida, geramos um thread para executar o outro métododummyMethod
na mesma instância única deApp
.Neste ponto, temos um conflito de bloqueio. O
fooMethodThreaded
já mantém o bloqueio em nossa instância deApp
nomeadoapp
. Portanto, quando o outro métododummyMethod
, sendosynchronized
, tenta obter o mesmo bloqueio no mesmoapp
objeto, ele encontra o bloqueio já obtido. Então osdummyMethod
blocos, esperando a fechadura ser liberada.E então ficamos sentados esperando. Enquanto isso, o thread original que mantém o bloqueio na
App
instância singular nomeadaapp
está em suspensão. Esse thread principal dorme por dez segundos. Ao final desses dez segundos, ofooMethodThreaded
método da nossaapp
instância é encerrado. O bloqueio éapp
então liberado, quando o método sincronizado é encerrado.Com o bloqueio original liberado, vemos uma cascata de entradas de log conforme o thread de segundo plano executa seu trabalho, com chamadas sucessivas para
dummyMethod
poder acessar o bloqueio aapp
cada vez.Observe que não vemos dez entradas no log para “não fazer nada em dummyMethod()”. Isso ocorre porque nosso
main
método mudoulog.forEach
antes que o thread em segundo plano fosse concluído.Solução
Se você realmente deseja executar esta tarefa dez vezes em threads em segundo plano, eu sugeriria algo como o código visto abaixo.
Se você quiser limitar a execução de nossas dez tarefas a apenas dez por vez, use algo diferente
synchronized
do método. Aprendemos que ter dois métodos na mesma classesynchronized
bloqueará um ao outro. Esse bloqueio de método duplo não é nosso objetivo. Portanto, use outra abordagem para bloquear. Uma abordagem é sincronizar em um objeto separado dentro do método, em vez de sincronizar no método. Veja esta resposta na pergunta Outra maneira de sincronizar o método . Eu preferiria usar uma abordagem explícita, conforme mostrado nesta resposta sobre a mesma pergunta. Veja também outra pergunta: o sincronizado no nível do método pode ser substituído pelo Lock? .No Java moderno, raramente abordamos
Thread
diretamente. Em vez disso, use a estrutura Executors adicionada ao Java 5.Aproveite o fato de que
ExecutorService
recentemente se tornouAutoCloseable
. Portanto, podemos usá-lo na sintaxe try-with-resources.Se sua tarefa não for CPU-bound, ou seja, se fizer algum bloqueio, utilize threads virtuais .
Com essa abordagem, vemos a demonstração começar e terminar no início e no final. E vemos toda e qualquer execução desejada de nossa tarefa fazer sua entrada no log.
Código completo do aplicativo: