Como definir acesso simultâneo para leitura e excluivo para escrita em um determinado objeto?

7 respostas
G

boa noite pessoal, no intuito de aprender a lidar com java "emulei" o seguinte problema:
um objeto q precisa ser acessado por várias threads, no entanto a concorrência maior é pela leitura do tal, mas tendo a consciência q alguma em algum momento precisará alterá-lo.

um objeto int [10];
uma thread ficará de tempo em tempo setando tds elementos desse vetor pra um valor qq;
as outras ficam o tempo td verificando se o valor em tds elemento é igual ao q ela espera;
se tiver algum elemento q é igual e algum outro q não é, é entendido como erro;

fiz o seguinte código:

public class TesteSync1 {

	private int[] valores = new int[10];
	private static final int QTDE_THREADS = 10;
		
	public void exec(){
		for (int i = 0; i < valores.length; i++) {
			valores[i] = 0;
		}
		
		for (int i = 0; i < QTDE_THREADS; i++) {
			new TesteThread(i+1).start();
		}
				
		new Thread(){
			public void run() {
				int vv = 0;
				while (true) {
					vv++;
					synchronized (valores) 
					{
						System.out.println("beginWrite: "+vv);
						for (int i = 0; i < valores.length; i++) {
							valores[i] = vv;
						}
						System.out.println("endWrite: "+vv);
					}
					TesteSync1.sleep(3000);					
					
					if (vv > QTDE_THREADS){
						vv = 0;
					}
				}
			};
		}.start();
	}
	
	private class TesteThread extends Thread{
		private int value;

		public TesteThread(int value) {
			this.value = value;			
		}
		
		private int countEquals(){
			int r = 0;
			for (int i = 0; i < valores.length; i++) {
				if (valores[i] == value) 
					r++;
				TesteSync1.sleep(100);
			}
			return r;
		}

		@Override
		public void run() {
			long tTmp = System.currentTimeMillis();
			long tIni = tTmp;
			
			int qq = 0;
			while (true) {
				synchronized (valores) 
				{
				int i = countEquals();
					if (i == vvalores.length){
						System.out.println("***** EQUAL: "+value);						
					} else if (i > 0){
						System.out.println("***** Erro: "+value+" "+i);
					} 										
				}

				qq++;
				long t2 = System.currentTimeMillis();
				if (t2 > tTmp + 10000){
					System.out.printf(">>> ******************Proc: %02d Qtde: %5d Tempo médio: %,5d mm\n", value, qq, (t2 - tIni) / qq);
					tTmp = t2;					
				}
			}
		}
		
		
	}
	
	public static void main(String[] args){
		new TesteSync1().exec();
	}

	static public void sleep(int ms){
		try {
			Thread.sleep(ms);
		} catch (InterruptedException e) {
			//e.printStackTrace();
		}	
	}
	
}

está funcionando. Porém dessa forma, somente uma thread por vez consegue acessar o vetor para leitura. Qual seria a melhor maneira q permita acesso simultâneo para leitura, porém acesso excluivo para escrita?

7 Respostas

E

Use um ReadWriteLock.

http://docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/locks/ReadWriteLock.html - a interface ReadWriteLock
http://docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/locks/ReentrantReadWriteLock.html - a classe que você tem de usar que é um lock do tipo “acesso simultâneo para leitura e exclusivo para escrita”.

G

obrigado entanglement, vou dar uma olhada.
eu tinha criado uma solução caseira aki, apesar de ter funcionando, não ousei postar aki não.

G

mas já q estamos aki é pra aprender mesmo, me desculpem por qq coisa tosca.

public class TesteSync3 {

	private int[] valores = new int[10];
	private static final int QTDE_THREADS = 10;
	private LockRW lock = new LockRW();
	
	public void exec(){
		for (int i = 0; i < valores.length; i++) {
			valores[i] = 0;
		}

		for (int i = 0; i < QTDE_THREADS; i++) {
			new TesteThread(i+1).start();
		}

		new Thread(){
			public void run() {
				int vv = 0;
				while (true) {
					vv++;
					lock.beginWrite();
					//synchronized (valores) 
					{
						System.out.println("beginWrite: "+vv);
						for (int i = 0; i < valores.length; i++) {
							valores[i] = vv;
						}
						System.out.println("endWrite: "+vv);
					}
					lock.endWrite();
					TesteSync3.sleep(3000);					

					if (vv > QTDE_THREADS){
						vv = 0;
					}
				}
			};
		}.start();
	}

	private class TesteThread extends Thread{
		private int value;

		public TesteThread(int value) {
			this.value = value;			
		}

		private int countEquals(){
			int r = 0;
			for (int i = 0; i < valores.length; i++) {
				if (valores[i] == value) 
					r++;
				TesteSync3.sleep(100);
			}
			return r;
		}

		@Override
		public void run() {
			long tTmp = System.currentTimeMillis();
			long tIni = tTmp;

			int qq = 0;
			while (true) {
				lock.beginRead();
				//synchronized (valores) 
				{
					int i = countEquals();
					if (i == valores.length){
						System.out.println("***** EQUAL: "+value);

					} else if (i > 0){
						System.out.println("***** Erro: "+value+" "+i);
					} 

				}
				lock.endRead();

				qq++;
				long t2 = System.currentTimeMillis();
				if (t2 > tTmp + 10000){
					System.out.printf(">>> ******************Proc: %02d Qtde: %5d Tempo médio: %,5d mm\n", value, qq, (t2 - tIni) / qq);
					tTmp = t2;					
				}
			}
		}
	}

	public static void main(String[] args) {
		new  TesteSync3().exec();
	}

	static public void sleep(int ms){
		try {
			Thread.sleep(ms);
		} catch (InterruptedException e) {
		}	
	}
}
public class LockRW {
	private int lockRead = 0;
	private int lockedWrite = 0;

	public void beginRead(){
		while (true) {
			synchronized (this) {
				if (lockedWrite == 0){					
					lockRead++;
					return;
				}
			}
			try {
				Thread.sleep(1);
			} catch (InterruptedException e) {
			}
		}
	}

	public void endRead(){
		synchronized (this) {
			if (lockRead > 0)					
				lockRead--;				
		}
	}

	public void beginWrite() {
		while (lockedWrite != 3) {
			synchronized (this) {
				lockedWrite = 1;
				if (lockRead == 0){					
					lockedWrite = 3;
					return;
				}
			}
			
		}
	}

	public void endWrite(){
		synchronized (this) {
			lockedWrite = 0;				
		}
	}	
}
E
As vantagens de usar essas classes prontas do JDK, em vez de usar as soluções caseiras, são as seguintes:

a) Se você usou as classes exatamente do jeito que foram documentadas, elas devem funcionar com certeza (o que você pode ter dificuldades de afirmar no caso de uma solução caseira - afinal de contas, você não é o dr. Doug Lea, que basicamente desenvolveu toda a parte de concorrência moderna do Java - o pacote java.util.concurrent);

b) Elas costumam ser muito mais mais rápidas que as soluções caseiras, porque em vez de usar synchronized, wait e notify (que são operações que usam monitores, que costumam ser muito pesados) usam alguns locks especiais que foram introduzidos no Java 5 justamente para acelerar os programas que precisam de multiprocessamento;

c) Usar um ReadWriteLock e outras classes do java.util.concurrent (em vez de usar um monte de synchronized, wait e notify) ajuda a tornar mais claros seus programas, porque você consegue então mostrar que está sendo usado um determinado recurso (por exemplo, o caso do multiple readers, single writer ) em vez de isso ficar implícito.
G

entanglement, obrigado pelo esclarecimento.

vc está certíssimo.

eu tinha feito esse "caseiro" no intuito de exercitar.
e q tb não conhecia esses q vc postou. agora até ja modifiquei pra funcionar com sua sujestão.

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class TesteSync4 {
	private int[] valores = new int[10];
	private static final int QTDE_THREADS = 10;
	private ReadWriteLock lock = new ReentrantReadWriteLock(); 
	public void exec(){
		for (int i = 0; i < valores.length; i++) {
			valores[i] = 0;
		}

		for (int i = 0; i < QTDE_THREADS; i++) {
			new TesteThread(i+1).start();
		}

		new Thread(){
			public void run() {
				int vv = 0;
				while (true) {
					vv++;
					lock.writeLock().lock();
					//synchronized (valores) 
					{
						System.out.println("beginWrite: "+vv);
						for (int i = 0; i < valores.length; i++) {
							valores[i] = vv;
						}
						System.out.println("endWrite: "+vv);
					}
					lock.writeLock().unlock();
					TesteSync4.sleep(3000);					

					if (vv > QTDE_THREADS){
						vv = 0;
					}
				}
			};
		}.start();
	}

	private class TesteThread extends Thread{
		private int value;

		public TesteThread(int value) {
			this.value = value;			
		}

		private int countEquals(){
			int r = 0;
			for (int i = 0; i < valores.length; i++) {
				if (valores[i] == value) 
					r++;
				TesteSync4.sleep(100);
			}
			return r;
		}

		@Override
		public void run() {
			long tTmp = System.currentTimeMillis();
			long tIni = tTmp;

			int qq = 0;
			while (true) {
				lock.readLock().lock();
				System.out.println("beginRead: "+value);
				//synchronized (valores) 
				{
					int i = countEquals();
					if (i == valores.length){
						System.out.println("***** EQUAL: "+value);

					} else if (i > 0){
						System.out.println("***** Erro: "+value+" "+i);
					} 

				}
				System.out.println("endRead: "+value);
				lock.readLock().unlock();
				
				qq++;
				long t2 = System.currentTimeMillis();
				if (t2 > tTmp + 10000){
					System.out.printf(">>> ******************Proc: %02d Qtde: %5d Tempo médio: %,5d mm\n", value, qq, (t2 - tIni) / qq);
					tTmp = t2;					
				}
			}
		}
	}

	public static void main(String[] args) {
		new  TesteSync4().exec();
	}

	static public void sleep(int ms){
		try {
			Thread.sleep(ms);
		} catch (InterruptedException e) {
		}	
	}


}
E

Eu considero “synchronized”, “wait” e “notify”, assim como disparar threads diretamente, em programação concorrente, os equivalentes do “goto” na programação estruturada.
O “goto” é uma primitiva de nível muito baixo que não deve ser usada (tanto é que no Java nem existe o “goto”).
Da mesma maneira, “synchronized”, “wait” e “notify”, assim como as threads, tendem a ser usados de forma incorreta e caótica, tornando os programas pouco compreensíveis e depuráveis.
Dentro da medida do possível, é melhor usar as classes de nível mais alto presentes no pacote java.util.concurrent.

G

mas não estou usando nenhum desses.

Criado 31 de julho de 2012
Ultima resposta 3 de ago. de 2012
Respostas 7
Participantes 2