Olá amigos, estou com um problema no uso de threads. Quando temos informações compartilhados por threads é preciso tomar alguns cuidados para que não aconteçam resultados indesejáveis.
Neste meu código:
public class Teste
{
public static void main( String argumentos[] )
{
PrimeThread p = new PrimeThread();
PrimeThread q = new PrimeThread();
p.start();
q.start();
}
}
class PrimeThread extends Thread
{
public static int x = 5;
public void run()
{
synchronized(this) {
PrimeThread.x++;
System.out.println(PrimeThread.x);
PrimeThread.x--;
System.out.println(PrimeThread.x);
}
}
}
Crio dois threads com o mesmo conteúdo e os executo. Quando um thread chega no synchronized(this) { ele deve executar todas as instruções deste bloco antes do outro thread continuar sua execução. Isso evita a intercalação de instruções e por consequência possíveis resultados indesejáveis.
A variável x inicia com o valor 5. O 1° primeiro thread a entrar no bloco synchronized(this) { acrescentaria 1 a essa variável, deixando ela com o valor 6. Este valor é impresso. Agora o valor da variável é decrementado, ficando 5. Novamente este valor é impresso. Somente após o término do bloco, o outro thread criado poderia continuar sua execução, entrar no seu bloco synchronized(this) { e repetir o mesmo processo do thread anterior.
O resultado da execução do programa deveria ficar assim:
6
5
6
5
Mas ele atinge com mais frequencia os resultados:
6
6
7
5
e
7
6
7
5
O que com certeza não é o esperado.
Será que o fato do meu processador possuir mais de um núcleo está causando esse problema? Se sim, como solucionar?
Eu testei este meu programa em um computador que possui apenas um processador, e este possuindo apenas um núcleo. Não acontecem resultados indesejáveis. Ou seja, esse meu problema com certeza tem haver com o meu processador de vários núcleos.
Existe alguma solução para isso?
Teria como limitar a execução dos dois threads a apenas um núcleo do processador?
O bloco synchronized funciona, o problema é que a jvm não garante a ordem de execução das threads.
Uma thread pode executar primeiro e mais vezes que outra.
Para isso funcionar vc teria que implementar um sistema de trava, em que uma thread só pode executar o método depois da outra.
O synchronized funciona sobre a instância que está dentro dos parênteses.
Como você fez synchronized (this), e cada runnable é um objeto diferente, não haverá sincronização. Afinal, cada thread poderá obter um monitor diferente (o this), e entrar no trecho sincronizado.
O problema é que sua variável x é estática. E, portanto, ela é compartilhada entre todas as threads. Por isso, para que isso funcione, você deveria usar um objeto também estático no trecho synchronized. Assim, todas as threads se sincronizariam sobre o mesmo monitor. Foi o que o colega do post acima fez.
Esse exemplo demonstra por que objetos estáticos são ruins em trechos multi-threads. Primeiro, porque é fácil alguém utiliza-la fora do bloco sincronizado (afinal, ela é global). E segundo, por que a sincronização dela fatidicamente levará ao enfileiramento de todas as threads que a compatilhem. Isso é só mais um fator que soma aos vários problemas que variáveis estáticas tem.
Testa esse exemplo identico ao seu. O primeiro os numeros não estarão ordenados, já no segundo sim. Somente trocando o this pela classe.
[code]package br.com.guj.forum;
public class PrimeThread {
public static void main(String argumentos[]) {
for (int i = 0; i < 10; i++) {
new PT().start();
}
}
static class PT extends Thread {
public static int x = 5;
@Override
public void run() {
synchronized (this) {
PT.x++;
System.out.println(PT.x);
PT.x--;
System.out.println(PT.x);
}
}
}
}[/code]
[code]package br.com.guj.forum;
public class PrimeThread {
public static void main(String argumentos[]) {
for (int i = 0; i < 10; i++) {
new PT().start();
}
}
static class PT extends Thread {
public static int x = 5;
@Override
public void run() {
synchronized (PT.class) { // So mudou isso
PT.x++;
System.out.println(PT.x);
PT.x--;
System.out.println(PT.x);
}
}
}
Novamente, a explicacação do código do lsjunior é a mesma. O objeto PT.class também é um objeto estático e, portanto, compartilhado por todas as classes. Você poderia até usar ali String.class que também iria funcionar, afinal, é outro objeto estático que seria compartilhado por todas as threads daquele monitor.
Entretanto, a solução do ericogr é ainda melhor, pois o objeto que ele usou como monitor não poderia ser acidentalmente usado por outra classe, ou num contexto errado.
Uma outra forma seria sincronizar apenas o metodo que exibe e altera o valor da variavel, o codigo fica mais simples.
[code]
package br.com.guj.forum;
public class PrimeThread {
public static void main(String argumentos[]) {
for (int i = 0; i < 10; i++) {
new PT().start();
}
}
static class PT extends Thread {
public static int x = 5;
@Override
public void run() {
this.print();
}
private synchronized void print() {
PT.x++;
System.out.println(PT.x);
PT.x--;
System.out.println(PT.x);
}
}