Tem como saber se a thread NAO conseguiu um lock?

Pessoal, nao sei se isso eh avancado propriamente - talvez eu esteja com a solucao na frente do nariz e nao esteja vendo. :smiley:

Seguinte - imagine que eu tenha um processo que pode ter apenas uma unica thread por vez rodando num determinado processo critico. Mas eu nao quero que as outras threads fiquem esperando - quero que simplesmente encerrem (se eu simplesmente sincronizasse o processo critico e ele demorasse muito, depois eu teria trocentas threads tentando pegar o lock).

Isto posto, pensei no seguinte (obvio):

  • tenta pegar um lock.
  • nao conseguiu, return.
  • caso chegue ate aqui, rodar processo critico.

Porem como saber que a thread nao conseguiu o lock? Ela simplesmente bloqueia, certo?

Entao fiz de outra forma - controlando isso no proprio lock. Ficou meio estranho, entao queria a opiniao de voces se eh isso mesmo, se tem outra forma, se estou viajando, etc.

[code]private static Boolean lock = new Boolean(false);

public void execute() {

synchronized (lock) {
    if (lock.booleanValue()) {
        return; // existe outra instancia rodando no processo critico
    }
    lock = new Boolean(true);
}

// processo critico

synchronized (lock) {
    lock = new Boolean(false);
}

}
[/code]

O ideal seria algo como (getLock() || return)… :smiley:

Marcio Kuchma

Fiz uns testes bestas e aparentemente funciona.

Simulador do tal processo critico e demorado:

[code]public class Processo {

private static Boolean lock = new Boolean(false);

public void initTask(String nome) {

    System.err.println("entrando - " + nome);
    
    synchronized (lock) {
        if (lock.booleanValue()) {
            System.err.println("saindo - nao conseguiu lock - " + nome);
            return;
        }
        System.err.println("conseguiu lock - " + nome);
        lock = new Boolean(true);
    }
    
    doTask();

    System.err.println("deixando lock - " + nome);
    synchronized (lock) {
        lock = new Boolean(false);
    }
        
    System.err.println("saindo - normal - " + nome);

}

private void doTask() {
    try {
        Thread.sleep(1);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

}[/code]

Apoio:

[code]public class Runner implements Runnable {

private String name;

public Runner(String name) {
    this.name = name;
}

public void run() {
    Processo task = new Processo();
    task.initTask(name);
}

public static void main(String[] args) {
    Thread t = null;
    for (int i = 1; i <= 20; i++) {
        t = new Thread(new Runner(String.valueOf(i)));
        t.start();
    }
}

}[/code]

A saida:

entrando - 2 conseguiu lock - 2 entrando - 3 saindo - nao conseguiu lock - 3 entrando - 4 saindo - nao conseguiu lock - 4 entrando - 5 saindo - nao conseguiu lock - 5 entrando - 6 entrando - 7 entrando - 1 entrando - 8 entrando - 9 entrando - 10 deixando lock - 2 saindo - nao conseguiu lock - 6 entrando - 11 saindo - nao conseguiu lock - 1 saindo - nao conseguiu lock - 8 saindo - nao conseguiu lock - 9 saindo - nao conseguiu lock - 10 saindo - normal - 2 conseguiu lock - 11 saindo - nao conseguiu lock - 7 entrando - 12 saindo - nao conseguiu lock - 12 entrando - 13 saindo - nao conseguiu lock - 13 deixando lock - 11 entrando - 14 conseguiu lock - 14 saindo - normal - 11 entrando - 15 saindo - nao conseguiu lock - 15 entrando - 16 saindo - nao conseguiu lock - 16 deixando lock - 14 saindo - normal - 14 entrando - 17 conseguiu lock - 17 entrando - 18 saindo - nao conseguiu lock - 18 entrando - 19 saindo - nao conseguiu lock - 19 entrando - 20 saindo - nao conseguiu lock - 20 deixando lock - 17 saindo - normal - 17

Interessante que a thread 14 pegou o lock apos a 11 deixa-lo, porem antes da 11 encerrar - isso nao eh problema, pois a 11 ja tinha saido da regiao critica.

Continuo aguardando opinioes. :smiley:

Marcio Kuchma

Não tem como fazer isso usando o monitor de cada objeto. Tua solução é usar o pacote http://gee.cs.oswego.edu/dl/ util.concurrent do Doug Lea. Ou então o equivamente java.util.concurrent do java 5.0.

Teu código passa a ficar +/- assim:

Mutex m = ...
if(m.attempt(0))
  try {
    ...
  finally { m.release(); }

Mas lembre de SEMPRE usar try/finally quando adquirir locks. Sempre, sempre, sempre. Ou então o teu código vai ter livelocks. Eu prometo que vai dar pau em produção na madrugada de sabado pra domingo se você não usar try/finally sempre.

Outra coisa, fica a dica de você verificar se não é mais negocio usar futures/executors.

Obrigado Louds. Realmente, usando o Mutex do util.concurrent o codigo ficou mais simples (embora alguem possa argumentar que nao esta no padrao :roll: ):

ProcessoDL (versao Doug Lea do Processo):

[code]import EDU.oswego.cs.dl.util.concurrent.Mutex;

public class ProcessoDL {

private static Mutex mutex = new Mutex();

public void initTask(String nome) {

    System.err.println("entrando - " + nome);
    
    try {
        if (!mutex.attempt(0)) {
            System.err.println("saindo - nao conseguiu lock - " + nome);
            return;
        }
    } catch (InterruptedException e) {
        e.printStackTrace();
        return;
    }

    System.err.println("conseguiu lock - " + nome);
    
    doTask();

    System.err.println("deixando lock - " + nome);
    mutex.release();
        
    System.err.println("saindo - normal - " + nome);

}

private void doTask() {
    try {
        Thread.sleep(20);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

}[/code]

Claro, em producao fica ainda mais simples, pois um ramo do if eh eliminado e cai fora todos esses prints. :smiley:

Quanto aos executors, ainda nao sera dessa vez - tenho que me aprofundar nesses “concurrent idioms” para entender toda a potencia do pacote do Doug Lea (e os novos recusos do 1.5 tambem).

Marcio Kuchma

kuchma, essa lib é um padrão de-facto. Sugeri ela por não saber se você tem acesso ao java 5.

Outra coisa, não esqueça do finally!!

Duvida besta: por que tem aquela verificacao se a thread foi interrompida antes de tentar pegar o lock no Mutex do DL? Nao consegui imaginar os efeitos de deixar a thread tentar pegar o lock mesmo ela tendo sido interrompida anteriormente. Eh alguma especie de verificacao de seguranca?

Marcio Kuchma

A idéia de interromper uma thread é que ela tome nota do fato o mais rápido possivel. wait() não vai lançar InterruptedException caso a thread já esteja interrompida e isso pode gerar uma demora sem limites por essa notificação.

Essa verificação existe para em um regisme de melhor esforço tentar evitar que aconteça uma pausa indesejada na thread.

Essa é uma preocupação muito importante quando se está usando o mecanismo de interrupt() para tentar minimizar o tempo de resposta, melhorar performânce e até evitar deadlocks.

Hmmm, legal - entao isso eh relacionado com a situacao em questao, certo? (como o Mutex do DL eh generico, a preocupacao eh muito valida)

Nesse processo que estou desenvolvendo nao uso esse mecanismo de interrupcao - vou remover essa verificacao propositalmente (e entao nao precisarei cuidar da excecao). Eh que eu ja tinha lido varias coisas a respeito, mas nao tinha prestado atencao no exato conceito de “interrupcao”, do ponto de vista da classe Thread. Sao interrupcoes geradas apenas por Thread.interrupt - nada de eventos externos, preempcao, etc.

Obrigado. :smiley:

Marcio Kuchma