Quais são os principais usos de yield () e como ele difere de join () e interrupt ()?

106

Estou um pouco confuso sobre o uso do yield()método em Java, especificamente no código de exemplo abaixo. Eu também li que yield () é 'usado para prevenir a execução de um thread'.

Minhas perguntas são:

  1. Eu acredito que o código abaixo resulta na mesma saída tanto ao usá-lo yield()quanto ao não usá-lo. Isso está correto?

  2. Quais são, de fato, os principais usos de yield()?

  3. De que forma é yield()diferente dos métodos join()e interrupt()?

O exemplo de código:

public class MyRunnable implements Runnable {

   public static void main(String[] args) {
      Thread t = new Thread(new MyRunnable());
      t.start();

      for(int i=0; i<5; i++) {
          System.out.println("Inside main");
      }
   }

   public void run() {
      for(int i=0; i<5; i++) {
          System.out.println("Inside run");
          Thread.yield();
      }
   }
}

Eu obtenho a mesma saída usando o código acima com e sem usar yield():

Inside main
Inside main
Inside main
Inside main
Inside main
Inside run
Inside run
Inside run
Inside run
Inside run
divz
fonte
Essa questão deve ser encerrada por ser muito ampla .
Raedwald
Não. Ele não retorna o mesmo resultado quando você tem yield()e não. quando você tem um i grande em vez de 5, pode ver o efeito do yield()método.
lakshman

Respostas:

97

Fonte: http://www.javamex.com/tutorials/threads/yield.shtml

janelas

Na implementação do Hotspot, a maneira que Thread.yield()funciona mudou entre Java 5 e Java 6.

No Java 5, Thread.yield()chama a chamada de API do Windows Sleep(0). Isso tem o efeito especial de limpar o quantum do segmento atual e colocá-lo no final da fila para seu nível de prioridade . Em outras palavras, todos os encadeamentos executáveis ​​da mesma prioridade (e aqueles de maior prioridade) terão a chance de ser executados antes que o encadeamento obtido receba o próximo tempo de CPU. Quando for finalmente reprogramado, ele voltará com um quantum totalmente cheio , mas não "transportará" nada do quantum remanescente do momento de ceder. Esse comportamento é um pouco diferente de um sono diferente de zero, em que o thread em espera geralmente perde 1 valor quântico (na verdade, 1/3 de um tique de 10 ou 15 ms).

No Java 6, esse comportamento foi alterado. O Hotspot VM agora implementa Thread.yield()usando a SwitchToThread()chamada de API do Windows . Essa chamada faz com que o thread atual desista de sua fatia de tempo atual , mas não de seu quantum inteiro. Isso significa que, dependendo das prioridades de outros threads, o thread de rendimento pode ser agendado de volta em um período de interrupção posterior . (Consulte a seção sobre agendamento de thread para obter mais informações sobre os períodos de tempo.)

Linux

No Linux, o Hotspot simplesmente chama sched_yield(). As consequências desta chamada são um pouco diferentes e possivelmente mais graves do que no Windows:

  • uma thread produzida não obterá outra fatia da CPU até que todas as outras threads tenham uma fatia da CPU ;
  • (pelo menos no kernel 2.6.8 em diante), o fato de que o encadeamento rendeu é implicitamente levado em conta pela heurística do escalonador em sua alocação de CPU recente - portanto, implicitamente, um encadeamento que rendeu pode receber mais CPU quando agendado em o futuro.

(Consulte a seção sobre agendamento de thread para obter mais detalhes sobre prioridades e algoritmos de agendamento.)

Quando usar yield()?

Eu diria praticamente nunca . Seu comportamento não é definido de forma padronizada e geralmente há maneiras melhores de realizar as tarefas que você deseja realizar com yield ():

  • se estiver tentando usar apenas uma parte da CPU , você pode fazer isso de uma forma mais controlável, estimando quanto CPU o thread usou em seu último bloco de processamento, em seguida, dormindo por algum tempo para compensar: consulte o método sleep () ;
  • se você está esperando que um processo ou recurso seja concluído ou se torne disponível, existem maneiras mais eficientes de fazer isso, como usar join () para esperar que outro thread seja concluído, usando o mecanismo de espera / notificação para permitir um thread para sinalizar para outro que uma tarefa foi concluída ou, de preferência, usando uma das construções de simultaneidade Java 5, como um semáforo ou fila de bloqueio .
Sathwick
fonte
18
"quantum restante", "quantum inteiro" - em algum lugar ao longo do caminho alguém esqueceu o que a palavra "quantum" significa
kbolino
@kbolino Quantum é o novo átomo.
Evgeni Sergeev
2
@kbolino - ... latin: "tanto quanto", "quanto" . Não vejo como isso seja de alguma forma contraditório com o uso acima. A palavra significa simplesmente uma quantidade descrita de alguma coisa, então dividi-la em partes usadas e restantes parece perfeitamente razoável para mim.
Periata Breatta
@PeriataBreatta Acho que faz mais sentido se você estiver familiarizado com a palavra fora da física. A definição de física era a única que eu conhecia.
kbolino
Eu coloquei uma recompensa nesta questão para obter esta resposta atualizada para 7, 8, 9. Edite-a com as informações atuais sobre 7,8 e 8 e você obterá a recompensa.
40

Vejo que a pergunta foi reativada com generosidade, perguntando agora quais são os usos práticos yield. Vou dar um exemplo de minha experiência.

Como sabemos, yieldforça o thread de chamada a desistir do processador no qual está sendo executado para que outro thread possa ser agendado para ser executado. Isso é útil quando o encadeamento atual concluiu seu trabalho por enquanto, mas deseja retornar rapidamente para o início da fila e verificar se alguma condição mudou. Como isso é diferente de uma variável de condição? yieldpermite que o thread retorne muito mais rápido ao estado de execução. Ao esperar por uma variável de condição, o encadeamento é suspenso e precisa aguardar um encadeamento diferente para sinalizar que deve continuar.yieldbasicamente diz "permitir a execução de um thread diferente, mas permita-me voltar ao trabalho logo, pois espero que algo mude em meu estado muito rapidamente". Isso sugere uma rotação ocupada, em que uma condição pode mudar rapidamente, mas suspender o encadeamento incorreria em um grande impacto no desempenho.

Mas chega de tagarelice, aqui está um exemplo concreto: o padrão paralelo da frente de onda. Uma instância básica desse problema é calcular as "ilhas" individuais de 1s em uma matriz bidimensional preenchida com 0s e 1s. Uma "ilha" é um grupo de células adjacentes umas às outras, vertical ou horizontalmente:

1 0 0 0
1 1 0 0
0 0 0 1
0 0 1 1
0 0 1 1

Aqui temos duas ilhas de 1s: superior esquerdo e inferior direito.

Uma solução simples é fazer uma primeira passagem por toda a matriz e substituir os valores 1 por um contador incremental de forma que no final cada 1 seja substituído por seu número de sequência na ordem principal da linha:

1 0 0 0
2 3 0 0
0 0 0 4
0 0 5 6
0 0 7 8

Na próxima etapa, cada valor é substituído pelo mínimo entre ele e os valores de seus vizinhos:

1 0 0 0
1 1 0 0
0 0 0 4
0 0 4 4
0 0 4 4

Agora podemos facilmente determinar que temos duas ilhas.

A parte que queremos executar em paralelo é a etapa em que calculamos os mínimos. Sem entrar em muitos detalhes, cada thread obtém linhas de uma maneira intercalada e depende dos valores calculados pelo thread que processa a linha acima. Assim, cada encadeamento precisa ficar ligeiramente para trás do encadeamento que processa a linha anterior, mas também deve se manter dentro de um tempo razoável. Mais detalhes e uma implementação são apresentados por mim neste documento . Observe o uso de sleep(0)que é mais ou menos equivalente a C de yield.

Nesse caso, yieldfoi usado para forçar cada thread a pausar, mas como o thread que processa a linha adjacente avançaria muito rapidamente nesse meio tempo, uma variável de condição seria uma escolha desastrosa.

Como você pode ver, yieldé uma otimização bastante refinada. Usá-lo no lugar errado, por exemplo, aguardar uma condição que muda raramente, causará uso excessivo da CPU.

Desculpe pela longa tagarelice, espero ter sido claro.

tudor
fonte
1
IIUC o que você apresenta no documento, a ideia é que, neste caso, é mais eficiente esperar ocupado, chamando yieldquando a condição não é satisfeita para dar aos outros threads chance de prosseguir com o cálculo, em vez de usar mais primitivos de sincronização de nível, certo?
Petr Pudlák
3
@Petr Pudlák: Sim. Eu comparei isso com o uso de sinalização de thread e a diferença de desempenho foi enorme neste caso. Uma vez que a condição pode se tornar verdadeira muito rapidamente (este é o problema principal), as variáveis ​​de condição são muito lentas, pois o thread é colocado em espera pelo sistema operacional, em vez de desistir da CPU por um período muito curto usando yield.
Tudor
@Tudor grande explicação!
Desenvolvedor Marius Žilėnas
1
"Observe o uso de sleep (0) que é mais ou menos o equivalente C do rendimento." .. bem, se você quiser sleep (0) com java, por que não usar isso? Thread.sleep () é algo que já existe. Não tenho certeza se essa resposta fornece um raciocínio sobre por que alguém usaria Thread.yield () em vez de Thread.sleep (0); Também existe um tópico explicando por que eles são diferentes.
eis
@eis: Thread.sleep (0) vs Thread.yield () está além do escopo desta resposta. Eu estava apenas mencionando Thread.sleep (0) para pessoas que procuram um equivalente próximo em C. A questão era sobre o uso de Thread.yield ().
Tudor,
12

Sobre as diferenças entre yield(), interrupt()e join()- em geral, não apenas em Java:

  1. cedendo : Literalmente, 'ceder' significa deixar ir, desistir, se render. Um encadeamento flexível informa ao sistema operacional (ou à máquina virtual, ou o que não seja) que ele deseja permitir que outros encadeamentos sejam agendados em seu lugar. Isso indica que ele não está fazendo algo muito crítico. No entanto, é apenas uma dica e não é garantido que tenha qualquer efeito.
  2. juntando : Quando vários threads 'se juntam' em algum identificador, token ou entidade, todos eles esperam até que todos os outros threads relevantes tenham concluído a execução (inteiramente ou até sua própria junção correspondente). Isso significa que vários threads concluíram suas tarefas. Em seguida, cada um desses threads pode ser agendado para continuar outro trabalho, podendo assumir que todas essas tarefas estão realmente concluídas. (Não deve ser confundido com SQL Joins!)
  3. interrupção : Usado por um tópico para 'cutucar' outro tópico que está suspenso, esperando ou entrando - de forma que seja agendado para continuar a funcionar novamente, talvez com uma indicação de que foi interrompido. (Não deve ser confundido com interrupções de hardware!)

Para Java especificamente, consulte

  1. Entrando:

    Como usar Thread.join? (aqui no StackOverflow)

    Quando juntar tópicos?

  2. Produzindo:

  3. Interrompendo:

    Thread.interrupt () é ruim? (aqui no StackOverflow)

einpoklum
fonte
O que você quer dizer com juntar um identificador ou token? Os métodos wait () e notificação () estão em Object, permitindo que um usuário espere em qualquer Object arbitrário. Mas join () parece menos abstrato e precisa ser chamado no Thread específico que você deseja terminar antes de continuar ... não é?
spaaarky21 de
@ spaaarky21: Eu quis dizer geralmente, não necessariamente em Java. Além disso, a wait()não é uma junção, é sobre um bloqueio no objeto que o thread de chamada está tentando adquirir - ele espera até que o bloqueio seja liberado por outros e tenha sido adquirido pelo thread. Ajustou minha resposta de acordo.
einpoklum
10

Primeiro, a descrição real é

Faz com que o objeto thread em execução no momento pause temporariamente e permite que outros threads sejam executados.

Agora, é muito provável que sua thread principal execute o loop cinco vezes antes que o runmétodo da nova thread seja executado, portanto, todas as chamadas de yieldacontecerão somente depois que o loop na thread principal for executado.

joinirá parar o thread atual até que o thread que está sendo chamado termine de ser join()executado.

interruptinterromperá a thread em que está sendo chamada, causando InterruptedException .

yield permite uma mudança de contexto para outras threads, de forma que esta thread não consuma todo o uso da CPU do processo.

MByD
fonte
+1. Observe também que, depois de chamar yield (), ainda não há garantia de que o mesmo thread não será selecionado para execução novamente, dado um pool de threads de prioridade igual.
Andrew Fielden
No entanto, SwitchToThread()chamar é melhor do que Sleep (0) e isso deve ser um bug em Java :)
Петър Петров
4

As respostas atuais estão desatualizadas e requerem revisão devido às mudanças recentes.

Não há diferença práticaThread.yield() entre as versões do Java desde 6 a 9.

TL; DR;

Conclusões baseadas no código-fonte do OpenJDK ( http://hg.openjdk.java.net/ ).

Se não levar em conta o suporte de HotSpot de sondas USDT (as informações de rastreamento do sistema estão descritas no guia dtrace ) e a propriedade JVM ConvertYieldToSleep, o código-fonte de yield()é quase o mesmo. Veja a explicação abaixo.

Java 9 :

Thread.yield()chama o método específico do sistema operacional os::naked_yield():
No Linux:

void os::naked_yield() {
    sched_yield();
}

No Windows:

void os::naked_yield() {
    SwitchToThread();
}

Java 8 e anterior:

Thread.yield()chama o método específico do sistema operacional os::yield():
No Linux:

void os::yield() {
    sched_yield();
}

No Windows:

void os::yield() {  os::NakedYield(); }

Como você pode ver, Thread.yeald()no Linux é idêntico para todas as versões do Java.
Vamos ver o Windows os::NakedYield()do JDK 8:

os::YieldResult os::NakedYield() {
    // Use either SwitchToThread() or Sleep(0)
    // Consider passing back the return value from SwitchToThread().
    if (os::Kernel32Dll::SwitchToThreadAvailable()) {
        return SwitchToThread() ? os::YIELD_SWITCHED : os::YIELD_NONEREADY ;
    } else {
        Sleep(0);
    }
    return os::YIELD_UNKNOWN ;
}

A diferença entre Java 9 e Java 8 na verificação adicional da existência do SwitchToThread()método da API Win32 . O mesmo código está presente no Java 6.
O código-fonte do os::NakedYield()JDK 7 é um pouco diferente, mas tem o mesmo comportamento:

    os::YieldResult os::NakedYield() {
    // Use either SwitchToThread() or Sleep(0)
    // Consider passing back the return value from SwitchToThread().
    // We use GetProcAddress() as ancient Win9X versions of windows doen't support SwitchToThread.
    // In that case we revert to Sleep(0).
    static volatile STTSignature stt = (STTSignature) 1 ;

    if (stt == ((STTSignature) 1)) {
        stt = (STTSignature) ::GetProcAddress (LoadLibrary ("Kernel32.dll"), "SwitchToThread") ;
        // It's OK if threads race during initialization as the operation above is idempotent.
    }
    if (stt != NULL) {
        return (*stt)() ? os::YIELD_SWITCHED : os::YIELD_NONEREADY ;
    } else {
        Sleep (0) ;
    }
    return os::YIELD_UNKNOWN ;
}

A verificação adicional foi descartada porque o SwitchToThread()método está disponível desde o Windows XP e Windows Server 2003 (consulte as notas do msdn ).

Gregory.K
fonte
2

Quais são, de fato, os principais usos de yield ()?

Yield sugere à CPU que você pode interromper a thread atual e começar a executar threads com prioridade mais alta. Em outras palavras, atribuir um valor de baixa prioridade ao segmento atual para deixar espaço para segmentos mais críticos.

Eu acredito que o código abaixo resulta na mesma saída ao usar o yield () e ao não usá-lo. Isso está correto?

NÃO, os dois produzirão resultados diferentes. Sem yield (), uma vez que a thread obtenha o controle, ela executará o loop 'Inside run' de uma vez. No entanto, com um yield (), uma vez que o encadeamento obtenha o controle, ele imprimirá a 'execução interna' uma vez e, em seguida, passará o controle para outro encadeamento, se houver. Se nenhum tópico estiver pendente, este tópico será retomado novamente. Portanto, toda vez que "Inside run" for executado, ele procurará outras threads para executar e, se nenhuma thread estiver disponível, a thread atual continuará em execução.

De que forma yield () é diferente dos métodos join () e interrupt ()?

yield () é para dar espaço a outras threads importantes, join () é para esperar que outra thread conclua sua execução e interrupt () é para interromper uma thread atualmente em execução para fazer outra coisa.

abbas
fonte
Só queria confirmar se esta afirmação é verdadeira Without a yield(), once the thread gets control it will execute the 'Inside run' loop in one go? Por favor, esclareça.
Abdullah Khan
0

Thread.yield()faz com que o thread passe do estado "Executando" para o estado "Executável". Nota: Não faz com que o thread vá para o estado "Esperando".

Prashant Gunjal
fonte
@PJMeisch, Não há RUNNINGestado para java.lang.Threadinstâncias. Mas isso não impede um estado de "execução" nativo para o encadeamento nativo para o qual uma Threadinstância é um proxy.
Solomon Slow
-1

Thread.yield ()

Quando invocamos o método Thread.yield (), o agendador de threads mantém a thread atualmente em execução no estado Runnable e escolhe outra thread de prioridade igual ou superior. Se não houver um thread de prioridade igual e superior, ele reprograma o thread de chamada yield (). Lembre-se de que o método de rendimento não faz com que o segmento vá para o estado Aguardar ou Bloqueado. Ele só pode fazer um thread de Running State para Runnable State.

Junte-se()

Quando o join é invocado por uma instância de thread, este thread dirá ao thread atualmente em execução para esperar até que o thread de junção seja concluído. Join é usado nas situações em que uma tarefa que deve ser concluída antes que a tarefa atual seja concluída.

Prashant Kumar
fonte
-4

O uso principal de yield () é colocar um aplicativo multi-threading em espera.

todas essas diferenças de métodos são yield () coloca o encadeamento em espera enquanto executa outro encadeamento e retorna após a conclusão desse encadeamento, join () trará o início dos encadeamentos em execução até o final e de outro encadeamento para executar após esse encadeamento terminado, interrupt () irá parar a execução de uma thread por um tempo.

K.Hughley
fonte
Obrigado pela sua resposta. No entanto, apenas repete o que as outras respostas já descrevem em detalhes. Estou oferecendo a recompensa por casos de uso adequados, onde yielddevem ser usados.
Petr Pudlák