AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / coding / Perguntas / 77325919
Accepted
Hong
Hong
Asked: 2023-10-20 01:27:59 +0800 CST2023-10-20 01:27:59 +0800 CST 2023-10-20 01:27:59 +0800 CST

Chame um método sincronizado com um novo thread em um método sincronizado

  • 772

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?

java
  • 2 2 respostas
  • 70 Views

2 respostas

  • Voted
  1. Best Answer
    Andrew S
    2023-10-20T04:09:23+08:002023-10-20T04:09:23+08:00

    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():

    package sandbox;
    
    public class SyncTest {
    
        public static void main(String[] args) throws InterruptedException {
            var test = new SyncTest();
            test.fooMethod();
        }
    
        private synchronized void dummyMethod() {
            System.out.println("do nothing in dummyMethod()");
        }
    
        private synchronized void fooMethod() throws InterruptedException {
            var t = new SyncTest(); // new instance will have its own monitor
            new Thread(() -> {
                for (int i = 0; i < 10; i++) {
                    t.dummyMethod();
                }
            }).start();
    
            Thread.sleep(10000);
            System.out.println("fooMethod() finished");
        }
    }
    
    • 3
  2. Basil Bourque
    2023-10-20T04:04:23+08:002023-10-20T04:04:23+08:00

    [Advertência: não sou especialista em simultaneidade Java.]

    Fiz uma versão completa e independente do seu código.

    Este código é usado CopyOnWriteArrayListcomo um log seguro para threads.

    package work.basil.example.threading;
    
    import java.time.Duration;
    import java.time.Instant;
    import java.util.SequencedCollection;
    import java.util.concurrent.CopyOnWriteArrayList;
    
    public class App
    {
        private SequencedCollection < String > log = new CopyOnWriteArrayList <> ( );  // Thread-safe `List` implementation.
    
        public static void main ( String[] args )
        {
            App app = new App ( );
            app.demo ( );
        }
    
        private void demo ( )
        {
            this.log.add ( "info | demo start | " + Instant.now ( ) );
    //        this.fooMethod ( );
            this.fooMethodThreaded ();
            this.log.add ( "info | demo end | " + Instant.now ( ) );
            log.forEach ( System.out :: println );
        }
    
        private synchronized void dummyMethod ( )
        {
            this.log.add ( "debug | do nothing in dummyMethod() | " + Instant.now ( ) );
        }
    
        private synchronized void fooMethod ( )
        {
            for ( int i = 0 ; i < 10 ; i++ ) dummyMethod ( );
            try { Thread.sleep ( Duration.ofSeconds ( 10 ) ); } catch ( InterruptedException e ) { throw new RuntimeException ( e ); }
            this.log.add ( "debug | fooMethod() finished | " + Instant.now ( ) );
        }
    
        private synchronized void fooMethodThreaded ( )
        {
            new Thread ( ( ) ->
            {
                for ( int i = 0 ; i < 10 ; i++ ) dummyMethod ( );
            } ).start ( );
            try { Thread.sleep ( Duration.ofSeconds ( 10 ) ); } catch ( InterruptedException e ) { throw new RuntimeException ( e ); }
            this.log.add ( "debug | fooMethodThreaded() finished | " + Instant.now ( ) );
        }
    }
    

    Quando executado:

    info | demo start | 2023-10-19T19:42:38.662271Z
    debug | fooMethodThreaded() finished | 2023-10-19T19:42:48.708631Z
    info | demo end | 2023-10-19T19:42:48.709687Z
    debug | do nothing in dummyMethod() | 2023-10-19T19:42:48.714712Z
    debug | do nothing in dummyMethod() | 2023-10-19T19:42:48.715071Z
    debug | do nothing in dummyMethod() | 2023-10-19T19:42:48.715137Z
    debug | do nothing in dummyMethod() | 2023-10-19T19:42:48.715205Z
    debug | do nothing in dummyMethod() | 2023-10-19T19:42:48.715264Z
    debug | do nothing in dummyMethod() | 2023-10-19T19:42:48.715321Z
    

    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, Appe ambos são declarados como synchronized. 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 :

    todos os blocos sincronizados do mesmo objeto podem ter apenas um thread executando-os ao mesmo tempo

    Você tem uma instância de Appexecução no thread principal. Lá executamos o fooMethodThreadedmétodo. Esse synchronizedmétodo bloqueia nossa única Appinstância. Em seguida, geramos um thread para executar o outro método dummyMethodna mesma instância única de App.

    Neste ponto, temos um conflito de bloqueio. O fooMethodThreadedjá mantém o bloqueio em nossa instância de Appnomeado app. Portanto, quando o outro método dummyMethod, sendo synchronized, tenta obter o mesmo bloqueio no mesmo appobjeto, ele encontra o bloqueio já obtido. Então os dummyMethodblocos, esperando a fechadura ser liberada.

    E então ficamos sentados esperando. Enquanto isso, o thread original que mantém o bloqueio na Appinstância singular nomeada appestá em suspensão. Esse thread principal dorme por dez segundos. Ao final desses dez segundos, o fooMethodThreadedmétodo da nossa appinstância é encerrado. O bloqueio é appentã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 dummyMethodpoder acessar o bloqueio a appcada vez.

    Observe que não vemos dez entradas no log para “não fazer nada em dummyMethod()”. Isso ocorre porque nosso mainmétodo mudou log.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 synchronizeddo método. Aprendemos que ter dois métodos na mesma classe synchronizedbloqueará 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 Threaddiretamente. Em vez disso, use a estrutura Executors adicionada ao Java 5.

    Aproveite o fato de que ExecutorServicerecentemente se tornou AutoCloseable. 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 .

    private void fooMethodWithExecutorService ( )
    {
        this.onlyTenTasksAtATimeLock.lock ( );
        try
        {
            try (
                    ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor ( ) ;
            )
            {
                int countTaskExecutions = 10;
                for ( int index = 0 ; index < countTaskExecutions ; index++ )
                {
                    executorService.submit ( this :: dummyMethod );
                }
            }
        }
        finally
        {
            this.onlyTenTasksAtATimeLock.unlock ( );
        }
    }
    

    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.

    info | demo start | 2023-10-19T21:19:42.158048Z
    debug | do nothing in dummyMethod() | 2023-10-19T21:19:42.165986Z
    debug | do nothing in dummyMethod() | 2023-10-19T21:19:42.166079Z
    debug | do nothing in dummyMethod() | 2023-10-19T21:19:42.166119Z
    debug | do nothing in dummyMethod() | 2023-10-19T21:19:42.166210Z
    debug | do nothing in dummyMethod() | 2023-10-19T21:19:42.166234Z
    debug | do nothing in dummyMethod() | 2023-10-19T21:19:42.166268Z
    debug | do nothing in dummyMethod() | 2023-10-19T21:19:42.166294Z
    debug | do nothing in dummyMethod() | 2023-10-19T21:19:42.166318Z
    debug | do nothing in dummyMethod() | 2023-10-19T21:19:42.166334Z
    debug | do nothing in dummyMethod() | 2023-10-19T21:19:42.166370Z
    info | demo end | 2023-10-19T21:19:42.166437Z
    

    Código completo do aplicativo:

    package work.basil.example.threading;
    
    import java.time.Duration;
    import java.time.Instant;
    import java.util.SequencedCollection;
    import java.util.concurrent.CopyOnWriteArrayList;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class App
    {
        private final SequencedCollection < String > log = new CopyOnWriteArrayList <> ( );  // Thread-safe `List` implementation.
        private final Lock onlyTenTasksAtATimeLock = new ReentrantLock ( );
    
        public static void main ( String[] args )
        {
            App app = new App ( );
            app.demo ( );
        }
    
        private void demo ( )
        {
            this.log.add ( "info | demo start | " + Instant.now ( ) );
    //        this.fooMethod ( );
    //        this.fooMethodThreaded ();
            this.fooMethodWithExecutorService ( );
            this.log.add ( "info | demo end | " + Instant.now ( ) );
            log.forEach ( System.out :: println );
        }
    
        private void fooMethodWithExecutorService ( )
        {
            this.onlyTenTasksAtATimeLock.lock ( );
            try
            {
                try (
                        ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor ( ) ;
                )
                {
                    int countTaskExecutions = 10;
                    for ( int index = 0 ; index < countTaskExecutions ; index++ )
                    {
                        executorService.submit ( this :: dummyMethod );
                    }
                }
            }
            finally
            {
                this.onlyTenTasksAtATimeLock.unlock ( );
            }
        }
    
        private synchronized void dummyMethod ( )
        {
            this.log.add ( "debug | do nothing in dummyMethod() | " + Instant.now ( ) );
        }
    
        private synchronized void fooMethod ( )
        {
            for ( int i = 0 ; i < 10 ; i++ ) dummyMethod ( );
            try { Thread.sleep ( Duration.ofSeconds ( 10 ) ); } catch ( InterruptedException e ) { throw new RuntimeException ( e ); }
            this.log.add ( "debug | fooMethod() finished | " + Instant.now ( ) );
        }
    
        private synchronized void fooMethodThreaded ( )
        {
            new Thread ( ( ) ->
            {
                for ( int i = 0 ; i < 10 ; i++ ) dummyMethod ( );
            } ).start ( );
            try { Thread.sleep ( Duration.ofSeconds ( 10 ) ); } catch ( InterruptedException e ) { throw new RuntimeException ( e ); }
            this.log.add ( "debug | fooMethodThreaded() finished | " + Instant.now ( ) );
        }
    }
    
    • 2

relate perguntas

  • Lock Condition.notify está lançando java.lang.IllegalMonitorStateException

  • Resposta de microsserviço Muitos para Um não aparece no carteiro

  • Validação personalizada do SpringBoot Bean

  • Os soquetes Java são FIFO?

  • Por que não é possível / desencorajado definir um lado do servidor de tempo limite de solicitação?

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    destaque o código em HTML usando <font color="#xxx">

    • 2 respostas
  • Marko Smith

    Por que a resolução de sobrecarga prefere std::nullptr_t a uma classe ao passar {}?

    • 1 respostas
  • Marko Smith

    Você pode usar uma lista de inicialização com chaves como argumento de modelo (padrão)?

    • 2 respostas
  • Marko Smith

    Por que as compreensões de lista criam uma função internamente?

    • 1 respostas
  • Marko Smith

    Estou tentando fazer o jogo pacman usando apenas o módulo Turtle Random e Math

    • 1 respostas
  • Marko Smith

    java.lang.NoSuchMethodError: 'void org.openqa.selenium.remote.http.ClientConfig.<init>(java.net.URI, java.time.Duration, java.time.Duratio

    • 3 respostas
  • Marko Smith

    Por que 'char -> int' é promoção, mas 'char -> short' é conversão (mas não promoção)?

    • 4 respostas
  • Marko Smith

    Por que o construtor de uma variável global não é chamado em uma biblioteca?

    • 1 respostas
  • Marko Smith

    Comportamento inconsistente de std::common_reference_with em tuplas. Qual é correto?

    • 1 respostas
  • Marko Smith

    Somente operações bit a bit para std::byte em C++ 17?

    • 1 respostas
  • Martin Hope
    fbrereto Por que a resolução de sobrecarga prefere std::nullptr_t a uma classe ao passar {}? 2023-12-21 00:31:04 +0800 CST
  • Martin Hope
    比尔盖子 Você pode usar uma lista de inicialização com chaves como argumento de modelo (padrão)? 2023-12-17 10:02:06 +0800 CST
  • Martin Hope
    Amir reza Riahi Por que as compreensões de lista criam uma função internamente? 2023-11-16 20:53:19 +0800 CST
  • Martin Hope
    Michael A formato fmt %H:%M:%S sem decimais 2023-11-11 01:13:05 +0800 CST
  • Martin Hope
    God I Hate Python std::views::filter do C++20 não filtrando a visualização corretamente 2023-08-27 18:40:35 +0800 CST
  • Martin Hope
    LiDa Cute Por que 'char -> int' é promoção, mas 'char -> short' é conversão (mas não promoção)? 2023-08-24 20:46:59 +0800 CST
  • Martin Hope
    jabaa Por que o construtor de uma variável global não é chamado em uma biblioteca? 2023-08-18 07:15:20 +0800 CST
  • Martin Hope
    Panagiotis Syskakis Comportamento inconsistente de std::common_reference_with em tuplas. Qual é correto? 2023-08-17 21:24:06 +0800 CST
  • Martin Hope
    Alex Guteniev Por que os compiladores perdem a vetorização aqui? 2023-08-17 18:58:07 +0800 CST
  • Martin Hope
    wimalopaan Somente operações bit a bit para std::byte em C++ 17? 2023-08-17 17:13:58 +0800 CST

Hot tag

python javascript c++ c# java typescript sql reactjs html

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve