Um requisito muito importante antes de começar a trabalhar com Threads, é conhecer e saber utilizar estes como Thread-Safe, garantindo assim uma aplicação robusta e sem inconsistências.
O conceito de Thread-Safe surgi quando há a necessidade de trabalhar-se com programação concorrente, seu principal objetivo é garantir que 2 ou mais threads que estejam em “condição de corrida” não obtenham informações erradas (condição de corrida ou race condition ocorre quando várias threads desejam acessar o mesmo recurso).
A solução para evitar o race condition, quando houver necessidade, é utilizar a exclusão mútua, ou seja, certificar-se que para aquele determinado recurso apenas 1 thread poderá utilizá-lo por vez.
Imagine que temos o Recurso A, e que a thread 1 e 2 desejam acessar esse recurso de forma concorrente. Garantindo a exclusão mútua ao Recurso A, se a Thread 1 começar o acesso ao Recurso A, ela só perderá o acesso ao mesmo quando terminar toda a tarefa que ela deseja, enquanto isso, a Thread 2 fica esperando.
Este conceito descrito acima (race condition e exclusão mútua é essencial para entender o conceito de Thread-Safe), então vamos a um exemplo mais prático:
Imagine que há um Sistema que possui dois procedimentos concorrentes (vamos chamá-lo de procedimento A e procedimento B), onde ambos fazem uso de um Arquivo chamado “registroDeVariavel.txt” e sempre antes de usá-lo ele é limpo pelo procedimento que vai escrever nele o que desejar. Esse arquivo serve para escrita e leitura de variáveis por apenas um procedimento, por isso sempre que um novo procedimento vai utilizá-lo, ele deve ser limpo para evitar inconsistências.
- O procedimento A acessa o arquivo e começa a realizar uma escrita no mesmo, porém no meio do processo o escalonador do processador interrompe sua execução e da prioridade ao procedimento B.
- Neste momento o procedimento B limpa o arquivo e começa a escrever o que ele precisa e termina toda sua execução.
- Após o término da escrita no arquivo pelo procedimento B, o procedimento A volta a ser executado e continua sua escrita normalmente, mas perceba aqui uma coisa super importante: O procedimento B apagou tudo que o A fez, então quando o A continuar a escrita o arquivo apresentará inconsistências.
Qual a solução para isso ? Usar Thread-Safe. Em Java podemos garantir a exclusão mútua através da palavra reservada “synchronized”, assim o procedimento B não poderá ser executado até o término do procedimento A.
Exemplo Prático com Thread-Safe
Na listagem 1 você pode ver um procedimento e java implementado com exclusão mútua, garantindo assim o comportamento Thread-Safe em nossa aplicação.
Listagem 1: Usando Synchronized
private synchronized void processarArquivo(String arquivo) {
//faça aqui seu procedimento de leitura e escrita do arquivo
}
Não é trivial desenvolver aplicações que usam a programação concorrente, isso porque todo o conceito de semáforo, exclusão mútua e condição de corrida estão envolvidos com threads. Este tipo de tarefa exige muita dedicação e análise, visto que um erro pode “bagunçar” toda a lógica da aplicação, e o pior é que descobrir onde está o erro é difícil, pois este é um erro semântico.
Thread-Safe, Single-Thread e Multi-Thread
Muita das vezes há confusão entre esses 3 conceitos, como já explicamos os tópicos anteriores o Thread-Safe garanti que não haverá conflito entre Threads concorrentes, ou seja, garante a segurança de aplicações que são Multi-Thread.
Uma aplicação Multi-Thread possui diversas Threads, ou seja, a programação concorrente se aplica aqui, várias tarefas simultaneamente, por outro lado o Single-Thread é uma aplicação que executa apenas 1 thread por vez. Atualmente é difícil, se não raro, ver uma aplicação que funcione apenas com Single-Thread, isso causaria gargalo em muitas aplicações que já funcionam com Multi-Thread.
Uma pergunta que muitos podem fazer é a respeito do “synchronized” do java: Se você usar esta palavra reservada está garantindo que apenas 1 thread por vez executará o seu processo, sendo assim sua aplicação não se tornaria Single-Thread ?
A resposta é não, o conceito de exclusão mútua não se adequa à aplicações Single-Thread. Note que Single-Thread é quando você tem apenas 1 Thread por vez sendo executada no processador, e nada mais. Mesmo que você use o “synchronized” ainda terá 2 ou mais tarefas no escalonador do processador, apenas aguardando seu momento para executar essa procedimento, além disso enquanto ela não executa a “região crítica” pois outro processo esta executando, ela pode fazer uma outra tarefa, então o conceito de Multi-Thread continua “vivo”.
Para assimilar mais ainda o conceito de Thread-Safe vamos ver uma classe correspondente ao padrão RGB implementando o Thread-Safe em blocos, ou seja, em vez de um método inteiro, podemos também utilizar em pedaços menores de código.
Listagem 2: Thread-Safe em blocos de código
public class RGBColor {
private int r;
private int g;
private int b;
public RGBColor(int r, int g, int b) {
checkRGBVals(r, g, b);
this.r = r;
this.g = g;
this.b = b;
}
public void setColor(int r, int g, int b) {
checkRGBVals(r, g, b);
synchronized (this) {
this.r = r;
this.g = g;
this.b = b;
}
}
/**
* returns color in an array of three ints: R, G, and B
*/
public int[] getColor() {
int[] retVal = new int[3];
synchronized (this) {
retVal[0] = r;
retVal[1] = g;
retVal[2] = b;
}
return retVal;
}
public synchronized void invert() {
r = 255 - r;
g = 255 - g;
b = 255 - b;
}
private static void checkRGBVals(int r, int g, int b) {
if (r < 0 || r > 255 || g < 0 || g > 255 ||
b < 0 || b > 255) {
throw new IllegalArgumentException();
}
}
}
CONCLUSÃO
Tenha em mente que o conceito de Thread-Safe é muito mais do que apenas a palavra reservada “synchronized” em Java, pois muitos acham que se resumo a isso. Este conceito serve não só para Java mas para outras linguagens que implementam programação concorrente, ou seja, possuem o recurso apropriado para criar um ambiente multi-thread.
Espero que tenham gostado do artigo, se tiverem alguma dúvida podem ficar a vontade em postar nos comentários que irei ter prazer em responder. Até a próxima