Pesquisar este blog

segunda-feira, 20 de maio de 2013

Paralelismo em Java com Callable

Paralelismo em Java com Callable

Pool de threads:

Na versão 5 do java, foram adicionadas classes para ajudar o desenvolvimento de threads e uma destas evoluções foram as pool d threads.
Uma pool de thread nada mais é do que uma maneira de administrar a execução das threads do seu programa. Caso você tenha 10000 threads para serem executadas, se você criar todas elas na mão e mandar executar fica difícil de administrar a execução de cada uma delas, colocando todas elas em um pool de threads, você pode controlar quantas serão executadas ao mesmo tempo e a máquina virtual é quem vai administrar a execução de todas as suas threads.
E como eu defino uma pool de threads?
ExecutorService executor = Executors.newFixedThreadPool(10);
No código acima, definimos uma pool de threads com tamanho fixo, existem várias outras maneiras de se definir uma pool de threads, mas aqui no nosso curso esta maneira é suficiente.
A grande vantagem de se usar pool de threads é que você pode determinar quantas threads serão executadas ao mesmo tempo no seu sistema, e a pool de threads é quem administra quem será executado e quando. Existe um cálculo que pode determinar qual é a quantidade ideal de threads de acordo com a sua máquina, mas para fazer este cálculo você tem que levar em conta o que o seu código faz. Porém, não adianta você colocar 10000 threads em uma máquina de 8 núcleos, que ela não vai conseguir executar todas as threads ao mesmo tempo, e isso provavelmente seja difícil de administrar. Já com as pool de threads, você cria, determina qual será a quantidade de threads executadas e pronto, ela administra pra você.
Estas pools, possuem métodos para receber as threads que serão executadas, como o método submit, este método retorna uma classe Future, que será explicada abaixo:
Future<Integer> futuro = executor.submit(new  ExemploCallable(i));
A classe future, possui um método chamado get, que quando executado, retorna o resultado do método de execução de uma thread, quando ele termina. Quando você chama o método get, ele fica esperando a thread acabar de executar. Como no nosso caso isso é imediato, não haverá nenhuma espera.
Múltiplas chamadas ao método get, não fará a thread executar várias vezes, só esperará o retorno do resultado da thread.
O método submit pode receber uma instancia de Runnable, ou de Callable. Quando passarmos uma instancia de runnable ele irá retornar nulo, ao final da execução do método, callable será explicada a seguir.
Até agora em nossos cursos trabalhamos apenas com a extensão da classe Thread ou a implementação da interface Runnable, mas e quando precisamos de um retorno do método da thread exectado, como faremos?

Interface Callable<T>

A interface Callable serve para resolver este problema, esta interface define um método call que possui um retorno e este retorno é definido pela classe T determinada na hora de implementar esta interface.

public class ExemploCallable implements Callable<Integer>{
A classe acima define que o ExemploCallable terá um método call retornando um Integer.
No exemplo abaixo definiremos uma maneira de implementar a soma de Fibonacci usando threads com a interface Callable<T>.
public class ExemploCallable implements Callable<Integer>{

    private Integer indice;

    public ExemploCallable(int indice) {
        this.indice=indice;
    }

    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(10);
        Set<Future<Integer>> set = new HashSet<Future<Integer>>();
        Future<Integer> futuro =null;
        for(int i=0;i<20;i++){
            futuro = executor.submit(new  ExemploCallable(i));
            set.add(futuro);
        }
        int soma = 0;
        try {
            for (Future<Integer> resultadoFuturo : set) {
                soma += resultadoFuturo.get();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        System.out.println("A soma índices é "+ soma);
        System.exit(soma);
    }

    @Override
    public Integer call() throws Exception {
        return indice;
    }
}
 Repare que para construir a classe ExemploCallable passamos como argumento um inteiro, sendo este inteiro o índice do nosso loop for.
O método call simplesmente retorna este valor, com o retorno deste método iremos realizar a soma de todos os valores, o que nada mais é do que um Fibonacci.

domingo, 5 de maio de 2013

Programacao concorrente synchronized

Programacao concorrente synchronized

Sincronizando seus metodos

Algumas vezes, quando estamos programando com threads, necessitamos que um determinado metodo seja acessado apenas por uma thread de cada vez, por exemplo, no banco de dados, os geradores de ids para tabelas, so podem ser acessados por apenas uma thread, senao poderiamos ter um resultado de ids repetidos para duas chamadas diferentes, o que geraria um erro em nosso banco.
Para fazer com que um metodo, ou mesmo uma parte do codigo, seja acessada por apenas uma thread, fazemos o uso da palavra chave synchronized, como mostrado abaixo:

public synchronized int getId(String nomeTabela){
       // codigo para retornar o proximo id de uma tabela
}
ou mesmo:

public int getId(String nomeTabela){
     System.out.println("Pegando proximo id da tabela" +nomeTabela);
     synchronized {
     // codigo para retornar o proximo id de uma tabela
     }
}
Como vimos existem duas maneiras de se usar a palavra synchronized, a diferenca entre elas e que, quando usada na assinatura do metodo, o metodo inteiro so podera ser acessado por apenas uma thread, ja da outra forma, apenas o codigo dentro demarcado pela palavra synchronized, tera o acesso limitado.
Bom, a primeira vista, isso parece uma grande maravilha, mas veja, a palavra synchronized mistura duas coisas antagonicas. Um estamos trabalhando com programacao concorrente, e agora estamos forcando a programacao concorrente voltar a ser executada por apenas uma thread, e isso e bem perigoso.
O grande perigo do uso da palavra synchronized sao os dead locks, mas o que e um dead lock?

Dead Lock

Um dead lock, ou tranca mortal, acontece quando temos duas threads em execucao, e as duas estao esperando o metodo que esta sendo executado acabar de executar, mas para que isso aconteca, o ouatro metodo devera terminar. No texto parece meio complicado de entender, entao vamos pra um exemplo de codigo de quando isso acontece:

public class Deadlock {
    static class Amigo {
        private final String nome;
        public Amigo(String nome) {
            this.nome = nome;
        }
        public String getNome() {
            return this.nome;
        }
        public synchronized void passar(Amigo passador) {
            System.out.format("%s: %s"
                + "  me passou a bola!%n", 
                this.nome, passador.getNome());
            passador.passaDeVolta(this);
        }
        public synchronized void passaDeVolta(Amigo passador) {
            System.out.format("%s: %s"
                + " voltou a bola pra mim!%n",
                this.nome, passador.getNome());
        }
    }

    public static void main(String[] args) {
        final Amigo neymar =
            new Amigo("Neymar");
        final Amigo ganso =
            new Amigo("Ganso");
        new Thread(new Runnable() {
            public void run() { neymar.passar(ganso); }
        }).start();
        new Thread(new Runnable() {
            public void run() { ganso.passar(neymar); }
        }).start();
    } 
}

Bom analisando o codigo acima, podemos ver que, muito provavelmente, quando executarmos esta classe, ela nunca ira terminar, e por que?
Uma das threads estara executando o metodo passaDeVolta, e ao terminar, ela deveria voltar a execucao do metodo passar, porem a outra thread estara parada no metodo passar, esperando o metodo passarDeVolta completar, e ai temos um lock mortal, ou seja, a thread 1 esperando a thread2 completar, e a thread2 para completar, precisa aguardar a thread1 completar.
Com o codigo, acontece exatamente o que esta acontecendo no cruzamento abaixo:
Aqui e bem facil achar o problema do codigo, agora em um sistema mais complexo isso nao e tao facil, pois as chamadas podem vir de varios lugares diferentes.
Usar thread, com synchronized nao e uma boa opcao em java, sempre que voce precisar usar o synchronized, e bem provavel que voce tenha problemas.