Thread em Java usando Synchronized

Bom dia, estou tentando representar um exercício simples para trabalhar com Threads em Java, hoje tentei implementar um programa que cria 4 Threads e cada uma acessa uma variável compartilhada que faz o incremento da mesma, a intenção é evitar que mais de uma thread acesse essa variável ao mesmo tempo usando a palavra reservada synchronized. Porém quando executo meu código varias vezes, há algumas situações que aparentemente mais de uma thread é acessada ao mesmo tempo. Por exemplo:

saída recebida:

Thread-3 2

Thread-0 1

Thread-1 2

Thread-2 3

pois a saída esperada era:

Thread-3 0

Thread-0 1

Thread-1 2

Thread-2 3

Não necessariamente nessa ordem de thread, mas obrigatoriamente os números em negrito devem ser únicos.

Gostaria de saber porque a palavra reservada synchronized não está surtindo efeito, e o código está executando como se não a tivesse.

Segue o código do programa:

Class ThreadRunnable.java:

package com.threads.implementacao;

import java.util.logging.Level;
import java.util.logging.Logger;

public class ThreadRunnable implements Runnable {

    private String nome;
    private int tempo;
    public static int i = -1;

    public ThreadRunnable(String nome, int tempo) {
        this.nome = nome;
        this.tempo = tempo;

        Thread t = new Thread(this); 
        t.start();
    }

    @Override
    public synchronized void run() {

        i++;
        System.out.println(Thread.currentThread().getName() + ":" + i);
        
        
    }

}

Class ThreadExecute.java:

package com.threads.implementacao;

public class ThreadExecute {

    public static void main(String[] args) {
        ThreadRunnable thread1 = new ThreadRunnable("#0", 500);
        ThreadRunnable thread2 = new ThreadRunnable("#1", 500);
        ThreadRunnable thread3 = new ThreadRunnable("#2", 500);
        ThreadRunnable thread4 = new ThreadRunnable("#3", 500);

        
        System.out.println(Thread.currentThread().getName());
    }

}
1 curtida

Quando você cria um método synchronized, ele sincroniza apenas na instância do respectivo objeto. Portanto, isto não se aplica a variáveis estáticas, já que elas pertencem à classe, e não às suas instâncias.

Para sincronizar o acesso à variável estática, você deve fazê-lo na classe, e não no método:

@Override
public void run() {
    synchronized (ThreadRunnable.class) {
        i++;
        System.out.println(Thread.currentThread().getName() + ":" + i);
    }
}

Ou então você cria um outro objeto estático que servirá como o lock, e faz a sincronização nele:

public class ThreadRunnable implements Runnable {
    private String nome;
    private int tempo;
    public static int i = -1;
    private static Object LOCK = new Object();

    public ThreadRunnable(String nome, int tempo) {
        this.nome = nome;
        this.tempo = tempo;
        Thread t = new Thread(this);
        t.start();
    }

    @Override
    public void run() {
        synchronized (LOCK) {
            i++;
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }
}
1 curtida

@hugokotsubo entendi agora, faz mais sentido.

Umas dúvidas me surgiram agora…

Eu também posso usar

synchronized (this) {
       .
       .
       .
    }

que terá o mesmo efeito certo ??

E outra…

Por que é que quando uso Thread.sleep(100); dentro do run() logo após o incremento as saídas tornam a se bagunçar ? Não era pra entrar apenas 1 thread por vez dentro do bloco que está o synchronized, idependentemente do tempo que eu colocar a thread para aguardar ?

Não. Isso vai sincronizar somente a instância, e não se aplicará às variáveis estáticas.


Se você fez isso com synchronized(this), não vai funcionar mesmo, pois como já disse, ele não vai sincronizar o acesso à variáveis estáticas.

Se fizer assim funciona:

public void run() {
    synchronized (ThreadRunnable.class) {
        try {
            System.out.println(Thread.currentThread().getName() + " antes do incremento");
            i++;
            System.out.println(Thread.currentThread().getName() + " antes do sleep");
            Thread.sleep(1000L);
            System.out.println(Thread.currentThread().getName() + " depois: " + i);
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
    }
}

Saída:

Thread-0 antes do incremento
Thread-0 antes do sleep
Thread-0 depois: 0
Thread-3 antes do incremento
Thread-3 antes do sleep
Thread-3 depois: 1
Thread-2 antes do incremento
Thread-2 antes do sleep
Thread-2 depois: 2
Thread-1 antes do incremento
Thread-1 antes do sleep
Thread-1 depois: 3

Repare que cada thread executa todo o bloco synchronized, inclusive o sleep.

1 curtida

entendi, muito obrigado !!!