Sincronismo com Thread

Estou implementando um programa que usa Threads para calcular o número de valores primos presente numa matriz, a intenção é usar uma variável global que seja acessível por qualquer thread e que isso cause uma inconsistência já que varias threads irão acessar ao mesmo tempo a mesma variável. Então por conta disso eu teria de implementar uma seção crítica com o Synchronized por exemplo, pare ele limitar uma thread por vez ao acesso a variável local. Porém acontece que mesmo sem o método Synchronized meu programa NÃO apresenta inconsistência, o que é contra intuitivo, gostaria que alguém me esclarecesse o que está havendo.

O programa precisa ser iniciado com alguns valores através do próprio menu:

  • o tamanho da matriz (opção 1) = 10
  • preenchimento automático da matriz (opção 2) = gera sempre a mesma matriz com 31 números primos
  • número de Threads (opção 3) = 4
  • iniciar criação de threads (opção 4)

Classe ThreadRunnable.java:

package com.threads.implementacao;

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

public class ThreadRunnable implements Runnable {

    public static int somaNumeroDePrimos = 0;
    private int iInicial, jInicial, iFinal, jFinal;

    public ThreadRunnable(int iInicial, int jInicial, int iFinal, int jFinal) {
        this.iInicial = iInicial;
        this.jInicial = jInicial;
        this.iFinal = iFinal;
        this.jFinal = jFinal;

        Thread t = new Thread(this); // mesmo que Thread t = new Thread(new ThreadRunnable ()); fora dessa classe
        t.start();
    }

    @Override
    public void run() {
        int i = iInicial, j = jInicial;
      
        while(i != iFinal || j != jFinal+1){

            if(j == ThreadExecute.TAM){
                j = 0;
                i ++;
            }

            //synchronized(ThreadRunnable.class){
                if (isPrimo(ThreadExecute.matrizDeNumeros.get(i).get(j)))
                    somaNumeroDePrimos++;
            //}
            j ++;
        } 
    }

    public boolean isPrimo(int numero) {

        if (numero <= 1)
            return false;

        for (int divisor = 2; Math.pow(divisor, 2) <= numero; divisor++)
            if (numero % divisor == 0)
                return false; // se achar algum divisor menor ou igual do que a raiz quadrada do proprio
                              // número, entao nao é primo

        return true;
    }

    public static int getQuantidadeDePrimos(){
        return somaNumeroDePrimos;
    }
}

ThreadExecute.java:

package com.threads.implementacao;

import java.util.ArrayList;
import java.util.Random;
import java.util.Scanner;

public class ThreadExecute {

    public static int TAM = 0;
    public static int numeroDeThreads = 0;
    public static ArrayList<ArrayList<Integer>> matrizDeNumeros;

    private static final int rangeNumerosAleatorios = 11; //0 - 10
    private static final Scanner in = new Scanner(System.in);
    private static int somaNumeroDePrimos = 0;

    public static void menu() {
        int opcao = 0;

        while (opcao != 6) {

            System.out.println("1 - Definir tamanho da matriz");
            System.out.println("2 - Preencher a matriz com numeros aleatorios");
            System.out.println("3 - Definir número de Threads");
            System.out.println("4 - Executar");
            System.out.println("5 - Vizualizar quantidade de números primos e tempo de execução");
            System.out.println("6 - Encerrar");

            opcao = in.nextInt();

            switch (opcao) {

                case 1:
                    definirTamanhoDaMatriz();
                    break;

                case 2:
                    preencherMatriz();
                    imprimirMatriz();
                    break;

                case 3:
                    definirNumeroDeThreads();
                    break;

                case 4:
                    criarThreads();
                    break;

                case 5:
                    numerosPrimosETempo();
                    break;

                case 6:
                    break;

                default:
                    System.out.println("Opção inválida");
                    break;

            }
        }

    }

    public static void numerosPrimosETempo(){
        System.out.println("A matriz possui = " + ThreadRunnable.getQuantidadeDePrimos());
    }

    public static void definirTamanhoDaMatriz() {
        System.out.println("Informe o tamanho da matriz sendo que a matriz terá o formato TAMxTAM:");
        TAM = in.nextInt();
    }

    public static void preencherMatriz() {
        Random numero = new Random(2); //inicia semente para sempre gerar a mesma matriz
        ArrayList<Integer> linha;

        if (TAM != 0) {
            matrizDeNumeros = new ArrayList<ArrayList<Integer>>(TAM);
            for (int i = 0; i < TAM; i++) {
                linha = new ArrayList<Integer>(); 

                for (int j = 0; j < TAM; j++)
                    linha.add(numero.nextInt(rangeNumerosAleatorios));

                matrizDeNumeros.add(linha);
            }
        } else
            System.out.println("Tamanho da matriz não definida");
    }

    public static void definirNumeroDeThreads() {
        System.out.println("Informe o numero de Threads que se deseja dividir a matriz:");
        numeroDeThreads = in.nextInt();
    }

    public static void criarThreads() {
        float iInicial, jInicial, iFinal, jFinal, indice;
        float numerosPorThreads;

        if (numeroDeThreads != 0 && matrizDeNumeros.size() != 0) {
            numerosPorThreads = (TAM * TAM) / numeroDeThreads;

            for (int n = 0; n < numeroDeThreads; n++) {
                // converte um dos termos para float pois divisão entre inteiros sempre retorna
                // inteiro
                iInicial = (n * numerosPorThreads) / TAM;
                jInicial = (n * numerosPorThreads) % TAM;

                indice = ((n + 1) * numerosPorThreads) / TAM;
                iFinal = ((indice * 10) % 10) == 0 ? (int)indice - 1 : (int)indice;

                indice = ((n + 1) * numerosPorThreads) % TAM;
                jFinal = indice == 0 ? TAM - 1 : (int)indice - 1;
                // converte para indices inteiros
                ThreadRunnable thread = new ThreadRunnable((int)iInicial, (int)jInicial, (int)iFinal, (int)jFinal);
            }

        } else
            System.out.println("Numero de Threads não definida ou matriz não preenchida");

    }

    public static void imprimirMatriz() {
        for (int i = 0; i < matrizDeNumeros.size(); i++) {
            for (int j = 0; j < matrizDeNumeros.get(i).size(); j++)
                System.out.print(matrizDeNumeros.get(i).get(j) + " ");

            System.out.println("");
        }
    }

    public static void main(String[] args) {

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

}

Não ha nada de contraintuitívo aqui, operações de leitura concorrente em uma variável que esteja se comportando como um imutável são seguras. No máximo você pode observar uma inconsistência na ordem dos resultados, o que só é inconsistente se a saída esperada precisa ser ordenada. Nesse caso, usar synchronized não resolveria, você precisaria de uma OrderedQueue ou PriorityQueue, dentre outras, a depender do tipo de scheduling que busca.

na verdade se eu aumentar o número de elementos na matriz, haverá maior concorrência entre as threads e a variável soma que é compartilhada entre todas elas terá um valor incoerente com a realidade, e daí sim faço o uso do synchronized ou LOCK na função afim de evitar essa inconsistência. Resolvi o problema, obrigado !

Agora que eu vi que você está querendo analisar o efeito em somaNumeroDePrimos e não em uma matriz compartilhada, realmente, neste caso é falta de repetição de amostragem. Você pode obter o efeito mesmo sem aumentar a matriz provavelmente, desde que aumente o número de amostragens.

Este tópico foi fechado automaticamente 3 dias depois da última resposta. Novas respostas não são mais permitidas.