“Thread-Safe” Classes

voltar

 
 

Este exemplo ilustra que apesar de uma classe ser considerada Thread Safe (classe Thing abaixo), o seu uso pode acarretar em um comportamento não esperado (não Thread Safe).

 
 

 
 

import java.util.List;

import java.util.ArrayList;

 
 

public class TestThread6 {

public static void main(String[] args) {

Names names = new Names();

names.addName(“Pedro”);

// Instancia e passa o nome para as Threads

UsaNomes t1 = new UsaNomes(“Thread-1”);

UsaNomes t2 = new UsaNomes(“Thread-2”);

// Seta a mesma coleção para ambas – names

t1.setCollection(names);

t2.setCollection(names);

// Inicia as Threads

t1.start();

t2.start();

}

}

 
 

class UsaNomes extends Thread {

private Names names;

 
 

public UsaNomes(String threadName) {

this.setName(threadName);

}

 
 

public void setCollection(Names names) {

this.names = names;

}

 
 

@Override

public void run() {

System.out.println(Thread.currentThread().getName() + ” UsaNomes.run”);

System.out.println(“\t\tREMOVEU: ” + this.names.removeFirstName());

}

}

 
 

// Classe 2 (classe Names que utiliza a classe Thing)

class Names {

private Thing<String> names = new Thing<String>();

 
 

public void addName(String name) {

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

+ ” Names.addName: ” + name);

names.add(name);

}

 
 

public String removeFirstName() {

if (names.size() > 0) {

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

+ ” Names.removeFirstName”);

return names.removeFirst();

} else {

System.out.println(Thread.currentThread().getName()+ ” Names.removeFirstName return null”);

return null;

}

}

}

 
 

// Classe 1 – Thread Safe – (criando classe com métodos Thread Safe)

// classe dita como “Thread Safe” pelos seus métodos serem sincronizados.

class Thing<T> {

private List<T> thing = new ArrayList<T>();

 
 

public synchronized void add(T name) {

this.thing.add(name);

}

public synchronized T removeFirst() {

try {

 
 

return thing.remove(0);

} catch (Exception ex) {

ex.printStackTrace();

}

return null;

}

public synchronized int size() {

return thing.size();

}

}

 
 

 
 

Outputs corretos:

Outputs com erro:

main Names.addName: Pedro

Thread-1 UsaNomes.run

Thread-1 Names.removeFirstName

REMOVEU: Pedro

Thread-2 UsaNomes.run

Thread-2 Names.removeFirstName return null

REMOVEU: null

main Names.addName: Pedro

Thread-1 UsaNomes.run

Thread-2 UsaNomes.run

Thread-2 Names.removeFirstName

REMOVEU: Pedro

Thread-1 Names.removeFirstName

java.lang.IndexOutOfBoundsException: Index: 0, Size: 0

at java.util.ArrayList.rangeCheck(ArrayList.java:570)

at java.util.ArrayList.remove(ArrayList.java:411)

at Thing.removeFirst(TestThread6.java:72)

at Names.removeFirstName(TestThread6.java:53)

at UsaNomes.run(TestThread6.java:35)                REMOVEU: null

main Names.addName: Pedro

Thread-1 UsaNomes.run

Thread-1 Names.removeFirstName

Thread-2 UsaNomes.run

REMOVEU: Pedro

Thread-2 Names.removeFirstName return null

REMOVEU: null

main Names.addName: Pedro

Thread-1 UsaNomes.run

Thread-2 UsaNomes.run

Thread-2 Names.removeFirstName

Thread-1 Names.removeFirstName

REMOVEU: Pedro

java.lang.IndexOutOfBoundsException: Index: 0, Size: 0

at java.util.ArrayList.rangeCheck(ArrayList.java:570)

at java.util.ArrayList.remove(ArrayList.java:411)

at Thing.removeFirst(TestThread6.java:72)

at Names.removeFirstName(TestThread6.java:53)

at UsaNomes.run(TestThread6.java:35)

REMOVEU: null

 
 

Análise:

 
 

Acima podemos observar 4 execuções do código sendo que 2 falharam. Isso ocorre porque apesar da classe Thing ser sincronizada (ela representa uma coleção sincronizada, equivalente a classe Vector), os seus métodos são sincronizados individualmente, ou seja, no momento em que uma thread está executando cada um deles, a outra thread fica esperando (waiting).

 
 

Porém, o método removeFirstName() da classe Names não é sincronizado e utiliza os métodos size() e removeFirst() da classe Thing. O que ocorre então, é que no método removeFirstName() há um pequeno ‘gap’ entre o início e o fim da execução do próprio método que permite que os métodos sincronizados (names.size e names.removeFirst) sejam executados por diferentes threads.

 
 

public String removeFirstName() { // NÃO SINCRONIZADO

if (names.size() > 0) { // SINCRONIZADO individualmente na classe Thing

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

+ ” Names.removeFirstName”);

return names.removeFirst(); // SINCRONIZADO individualmente na classe Thing

} else {

System.out.println(Thread.currentThread().getName()+ ” Names.removeFirstName return null”);

return null;

}

}

 
 

Solução:

 
 

// Etapa 2 (classe que utiliza a classe abaixo “com métodos Thread Safe”)

class Names {

private Thing<String> names = new Thing<String>();

 
 

public synchronized void addName(String name) {

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

+ ” Names.addName: ” + name);

names.add(name);

}

 
 

public synchronized String removeFirstName() { // O método inteiro é sincronizado

if (names.size() > 0) {

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

+ ” Names.removeFirstName”);

return names.removeFirst();

} else {

System.out.println(Thread.currentThread().getName()+ ” Names.removeFirstName return null”);

return null;

}

}

}

 
 

Análise:

 
 

No código acima, fica demonstrado que o método removeFirstName da classe Names é inteiro sincronizado, ou seja, uma vez que ele começa a ser executado por uma thread ele não será interrompido.

 
 

Anúncios
Esta entrada foi publicada em Threads com as etiquetas . ligação permanente.

Deixe uma Resposta

Preencha os seus detalhes abaixo ou clique num ícone para iniciar sessão:

Logótipo da WordPress.com

Está a comentar usando a sua conta WordPress.com Terminar Sessão /  Alterar )

Google+ photo

Está a comentar usando a sua conta Google+ Terminar Sessão /  Alterar )

Imagem do Twitter

Está a comentar usando a sua conta Twitter Terminar Sessão /  Alterar )

Facebook photo

Está a comentar usando a sua conta Facebook Terminar Sessão /  Alterar )

Connecting to %s