Thread come tudo a memória

18 respostas
M

Pessoal,

Fiz um programinha para ler um arquivo e inserir no banco, e funcionou tudo certo, só que meu arquivo é muito grande então estava demorando muito para inserir no banco optei em fazer ele com thread e vai bem mais rápido, só que após algum tempo rodando ele da o seguinte erro:

Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread at java.lang.Thread.start0(Native Method) at java.lang.Thread.start(Thread.java:597) at Main.main(Main.java:46)

Ta claro pela mensagem que o java comeu toda a memória que ele tinha, mas gostária de saber como poderia fazer para controlar isso, para ele não abrir mais threads do que o computador suporta ?

Obrigado,

Marcelo Gomes

18 Respostas

V

Voce disse que criou uma thread para rodar em background e ler seu arquivo, enquanto isso vc nao precissa para de usar sua aplicacao, certo ?
Nao era para ocorrer este erro. Tem como postar o codigo que vc criar a thread e o q a thread esta executando ?

M

O Main

No main estou usando o BioJava para fazer o parser do arquivo, para cada linha do arquivo abro um thread para fazer o insert no banco.

public class Main {

    public static void main(String[] args) throws NoSuchElementException, BioException, FileNotFoundException, IOException {
        SequenciaService sequenciaService = new SequenciaService();
        String fileName = "fasta.txt";
        BufferedReader br = null;
        br = new BufferedReader(new FileReader(fileName));
        SequenceIterator stream = SeqIOTools.readFastaDNA(br);
        Thread salvar = null;
        while(stream.hasNext()){
            org.biojava.bio.seq.Sequence seq = stream.nextSequence();
            Sequencia sequenciaArquivo = new Sequencia();
            sequenciaArquivo.setNome(seq.getName());
            sequenciaArquivo.setSequencia(seq.seqString());
            salvar = new Thread(new insert(sequenciaArquivo));
            salvar.start();
        }
        try {
            salvar.join();
        } catch (InterruptedException ex) {
            Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
            System.out.println("Deu erroeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee");
        }
        System.out.print("fim");
        br.close();
}

Classe insert que insere no banco com JPA

public class insert implements Runnable{
    private volatile Thread rodavar = null;
    private SequenciaService serquenciaService;
    private Sequencia sequenciaArquivo;

    insert(Sequencia sequenciaArquivo) {
        this.sequenciaArquivo = sequenciaArquivo;
    }

   public void start(){
        if(rodavar == null){
            rodavar = new Thread(this, "insert");
            rodavar.start();
        }
    }

    public void run() {
        Thread.yield();
        serquenciaService = new SequenciaService();
        Sequencia sequencia = serquenciaService.Salvar(getSequenciaArquivo());
    }
R

Já que você está criando uma Thread para cada linha do arquivo, certamente haverá problema se o arquivo for grande demais, pois o excesso de threads irá consumir toda a memória disponível da JVM. Há uma discussão interessante sobre isso no link abaixo:

http://answers.google.com/answers/threadview/id/344545.html

Então, acho que seria interessante você limitar o número máximo de threads simultâneas. Em ambiente Windows, por exemplo, a recomendação é usar até cerca de 16 threads ao mesmo tempo.

S

que tal um sleep(tempo) na thread?

M

Roger,

Esta quantidade de thread teria que ser fixa ? como saber o máximo que poderia usar ? Sabe de algum design pattern para trabalhar com threads neste esquema ?

Obrigado,

Marcelo Gomes

M

Sandeco.

Não entendo pq usar o sleep poderia me ajudar, ele só não vai mandar a thread dormir por um tempo X ??? a thread mesmo dormindo iria ficar segurando a memória não eh ?? e meu sistema ia ficar mais lento pois iria ficar dormindo x tempo.

Obrigado,

Marcelo Gomes

R

Sim, sugiro usar um valor fixo para o número máximo de threads simultâneas. Já enfrentei o seguinte problema: para emitir um relatório, tinha de realizar 4 consultas SQL para cada mês solicitado, e em seguida conciliar via programação os dados retornados pelas consultas. Assim, se o relatório precisasse cobrir um ano inteiro, portanto 12 meses, tinha de executar 12 x 4 = 48 consultas SQL no total. Executar todas estas consultas uma após a outra demorava muito, e se executasse todas ao mesmo tempo certamente derrubaria o desempenho do servidor de Banco de Dados. Para resolver o problema, criei uma classe de thread para executar uma única consulta SQL. Para 48 consultas, tínhamos portanto 48 threads. Então, criei uma outra classe de thread para arbitrar a execução destas 48 threads, de modo que não mais do que 3 delas executassem ao mesmo tempo. Quando uma das threads SQL encerrava a execução, a thread árbitra era notificada, e portanto podia disparar uma outra thread SQL. O desempenho do relatório melhorou muito e não tive de torturar (muito) o servidor de Banco de Dados.

M

Entendi, tem como me dar uma dica de como fazer este controle do numero de threads?

Obrigado,

Marcelo Gomes

V

Uma solucao simples para vc testar seria, coloque uma variavel int static na classe da thread, incremente 1 a cada start, e dentro do while so execute o bloco de codigo se a variavel for < 16, q seria a quantidade de threads q deseja executar, e apos a linha Sequencia sequencia = serquenciaService.Salvar(getSequenciaArquivo()); subtraia 1 do contador. Lembre-se de criar metodos para manipular a variavel sincronizados.
Desta forma vc pode verificar se o desempenho é o esperado…

M

Funcionou…

na classe insert criei a variavel quantidade como static.

public static int qtd;

E no main ficou com um do/while para segurar ele…

insert.qtd = 0;
        while(stream.hasNext()){
            org.biojava.bio.seq.Sequence seq = stream.nextSequence();
            Sequencia sequenciaArquivo = new Sequencia();
            sequenciaArquivo.setNome(seq.getName());
            sequenciaArquivo.setSequencia(seq.seqString());
            do{
                System.out.println("Aguardando..." + insert.qtd);
            } while (insert.qtd >= 16);
            salvar = new Thread(new insert(sequenciaArquivo));
            salvar.start();
        }

Mas não gostei muito desta foram, fico desperdiçando recurso de processamento com este do/while …

Alguma outra dica ?

Bom mas já vou adiantar o obrigado a todos pois embora acredito que não seja a melhor forma já eh uma forma que funciona!!! =]

R

Segue em anexo um programa que cria 10 threads e executa no máximo 3 delas de cada vez, espero que seja útil. O projeto foi criado no NetBeans 6.5.1.

V

E ai cara… veja esse link q vai resolver seu problema de uma forma bem melhor…

http://www.dsc.ufcg.edu.br/~jacques/cursos/map/html/threads/pool.html

R

Olá,

Você ta usando java5? Se sim, use thread pool.

Veja um exemplo simples (baseado em seu código)

ExecutorService pool = Executors.newFixedThreadPool(QUANTIDADE_DE_THREADS_QUE_DESEJA);
 while(stream.hasNext()){  
             org.biojava.bio.seq.Sequence seq = stream.nextSequence();  
             Sequencia sequenciaArquivo = new Sequencia();  
             sequenciaArquivo.setNome(seq.getName());  
             sequenciaArquivo.setSequencia(seq.seqString());  
             pool.execute(sequenciaArquivo); //considerando que essa classe Sequencia implemente Runnable
 }  

 pool.shutdown();

[]´s

M

Ficou perfeito… ou quase …

Por tentativa e erro cheguei no número mágico de 1000 threads rodando ao mesmo tempo, teria alguma forma matematica de descobrir quantas thred o sistema iria conseguir rodar ao mesmo tempo ?

Mas uma vez muito obrigado mesmo!!! vocês são fera!!! :smiley:

V

1000 threads eh coisa em, as vezes o aumento do numero de threads nao reflete no aumento de velocidade. Seria interessante vc verificar o tempo q 1000 threads leva para ler seu arquivo, e depois baixe para 500 e faca o mesmo teste. Ja tive aplicações q coloquei 100 threads para rodar e foi mais lento do q deixar 20.

R

Reforço o comentário do vdb e sugiro que, se for importante para você experimentar vários valores para a quantidade máxima de threads simultâneas, pode ser interessante obter essa quantidade a partir de um arquivo de configuração.

M

ok, vou gerar os valores para 1000, 500 e 100 e posto aqui :smiley:

obrigado,

Marcelo Gomes

D

Olá Marcelo,

o seu problema me interessou mais pelo fato de você estar utilizando a biblioteca biojava do que pelas questões de thread.
Por um acaso você sabe me dizer se o desempenho dela é comparável com bioperl ou biopython? Você está utilizando ela para montar genomas?

Com relação ao seu problema, eu acho que a questão não se resolve simplesmente aumentando o número de threads, mas identificando onde o gargalo está ocorrendo. Pelo seu código, posso sugerir duas opções:

Na parte de acesso ao arquivo, utilize buffer, algo como:

BufferedReader in = new BufferedReader(new FileReader(arquivo), 32768); // 32k buffer

Outro ponto, se você criar uma classe (thread tb) que tenha uns 5 ou 10 objetos (5 ou 10 threads) consumindo uma fila de objetos a serem inseridos no banco, e utilizar o preparedstatement ao invés do jpa, certamente será mais rápido. Outra possibilidade seria tentar utiliza a atualização em batch:
http://java.sun.com/j2se/1.3/docs/guide/jdbc/spec2/jdbc2.1.frame6.html

fw

Criado 1 de junho de 2009
Ultima resposta 4 de jun. de 2009
Respostas 18
Participantes 6