Problema Lector Escritor Threads

Turco

Estoy con hilos y no termino de dar pie con bola. Por un lado tengo un ejercicio de parking que me funciona bien con monitores, pero no con semáforos, y por otro, estoy con este problema de lectores y escritores que no logro terminar.

He buscando por internet hasta llegar a entender lo que tengo hasta ahora, pero me falta el código en el que los escritores escriben en el fichero y en el que los lectores leen del fichero.

Por si alguno no sabe de que va el problema, así a grandes rasgos trata de que existen escritores y lectores, los cuales escriben y leen en un archivo. Mientras haya un escritor, los lectores permaneceran a la espera, y viceversa. Los lectores leerán del archivo a partir de donde lo dejó el último lector, es decir, que si un lector leyó hasta la "i" de la palabra "cielo", el siguiente tendrá que empezar a partir de la "e".

No se asusten por el código, la mayor parte es por comentarios xD.
Aquí está lo que tengo hasta ahora, a ver si alguien me puede ayudar.

Clase Lector.

public class Lector extends Thread{
	private static Random r = new Random();
	private LibroPreferenciaEscritores libro;
	private int id;
	
public Lector(LibroPreferenciaEscritores libro, int id){
	this.libro = libro;
	this.id = id;
}

public void run(){
		/*Como en el método empezarLeer de la clase Libro hay un throw/try, aquí hay que
		 * implementarlo también.
		 */
		try {
			libro.empezarLeer(id);
			//implementar leer
			Thread.sleep(r.nextInt(5000));
		
		
			libro.terminarLeer(id);
			Thread.sleep(r.nextInt(5000));
		} catch (InterruptedException e) {}
	}
}

Clase Escritor. Lo que hace en la parte de escribir en el fichero, es escribir hasta que el usuario le meta una "#". El caso es que he estado pasándole el debugger, y cuando llega a la línea de FileWriter, me salta al final del método Run(). Además, al incluir las líneas donde se escribe en el fichero, en la clase simulador, sólo entran al programa Lectores, a excepción de cuando ejecuto el debugger que puedo forzar los escritores.

public class Escritor extends Thread {
	public static final String filename = "./unidad 2/actividad 3/libro.txt";
	private static Random r = new Random();
	private LibroPreferenciaEscritores libro;
	private int id;
	Scanner teclado = new Scanner(System.in);
	
public Escritor(LibroPreferenciaEscritores libro, int id){
	this.libro = libro;
	this.id = id;
}

public void run(){
		/*Como en el método empezarLeer de la clase Libro hay un throw/try, aquí hay que
		 * implementarlo también.
		 */
		try {
			libro.empezarEscribir(id);
			
			File f = new File(filename);
			FileWriter fw = new FileWriter(f);
			BufferedWriter bw = new BufferedWriter(fw);
			
			String frase = teclado.nextLine();
			
			while (!frase.equals("#")){
				bw.write(frase + "\n");
			}
			
			bw.flush();
			bw.close();
			fw.close();
			
			
			//implementar escribir
			Thread.sleep(r.nextInt(5000));
		
			libro.terminarEscribir(id);
			Thread.sleep(r.nextInt(5000));
		} catch (InterruptedException | IOException e) {}
}
}

Clase Libro.

public class LibroPreferenciaEscritores {
	public static final String filename = "./unidad 2/actividad 3/libro.txt";
	private int nLectores = 0;
	private boolean hayEscritor = false;
	private int nEscritores = 0;
	Scanner teclado = new Scanner(System.in);
	
public synchronized void empezarLeer(int id) throws InterruptedException{
	/*Mientras que haya un escritor en el fichero o haya escritores esperando,
	 *  los lectores tendrán que esperar. Aquí está una de las dos diferencias
	 *  con la clase Libro.
	 */
	while(hayEscritor || nEscritores > 0){
		wait();
	}
	
	nLectores++;
	System.out.println("El LECTOR " + id + " está ACCEDIENDO al fichero.");
}

//Se sincroniza para evitar acceder a nLectores concurrentemente, de manera que se cierren dos lectores a la vez.
public synchronized void terminarLeer(int id){
	System.out.println("El LECTOR " + id + " está SALIENDO del fichero.");
	nLectores--;
	
	/*Cuando el número de lectores sea 0, se notifica que ya se puede acceder nuevamente
	 * al fichero. La diferencia con el notifiAll(), es que en este caso, se notificaría
	 * a los escritores ya que son los únicos que estarían esperando (hay que recordar
	 * que puedenn haber varios lectores simultáneamente y por tanto no esperan si hay otro
	 * lector dentro). 
	 * 
	 * Aquí está la otra diferencia con la clase Libro, donde se implementará un notifyAll()
	 * y es que esta vez, en vez de avisar sólo a un lector o un escritor (quien este a la cola),
	 * avisaremos a todos los que esten esperando.
	 */
	if(nLectores == 0)
		notifyAll();
}

public synchronized void empezarEscribir(int id) throws InterruptedException{
	nEscritores++;
	//Mientras que haya un escritor en el fichero o algún lector, el escritor tendrá que esperar.
	while(hayEscritor || nLectores > 0) {
		wait();
	}
	//Cuando entra el escritor, se pone la variable a true y así se evite que entren lectores.
	hayEscritor = true;
	System.out.println("El ESCRITOR " + id + " está ACCEDIENDO al fichero.");
}

public synchronized void terminarEscribir(int id){
	nEscritores--;
	System.out.println("El ESCRITOR " + id + " está SALIENDO al fichero.");
	//Se pone a false la variable hayEscritor para que pueda entrar otro escritor o lectores.
	hayEscritor = false;
	/*Cuando sale un escritor se notifica a todo el mundo de manera que cualquiera,
	 * ya sea escritor o lector (de todos los que esperan), puedan entrar. Si pusiera notify(),
	 * pondría a la espera a escritores o lectores, ya que sólo estaría avisando a uno de ellos
	 * (aleatoriamente).
	 */
	notifyAll();
}
}

Clase Simulador.

public class Simulador {

public static void main(String[] args) {
	LibroPreferenciaEscritores libro = new LibroPreferenciaEscritores();
	Escritor[] esc = new Escritor[2];
	Lector[] lec = new Lector[10];
	
	for(int i = 0; i < esc.length; i++)
		esc[i] = new Escritor(libro, i);
	
	for(int i = 0; i < lec.length; i++)
		lec[i] = new Lector(libro, i);
	
	for(int i = 0; i < esc.length; i++)
		esc[i].start();
	
	for(int i = 0; i < lec.length; i++)
		lec[i].start();

}

}
willy_chaos

Revisa el mp que te he enviado

JuAn4k4

Ojo con el sleep. Nunca terminas de leer ni de escribir.

InterrumpedException salta después del primer sleep, y saltas al catch y nunca se ejecuta lo que va después. Create una función que haga el sleep:

function sleep(long time) { 
 try { Thread.sleep(time); } catch (InterrumpedException ignored){}}
}

No lo he leido muy detenidamente de todas formas.

E

Te dejo aquí una implementación que hice el año pasado. Es algo diferente, pero el concepto es el mismo.

Por cierto, ojito con los despertares espúreos, que te tiran todo el ejercicio abajo...

package ejercicio.ejer5_4;

public class ProdConsBuffer {

private static final int PRODUCTORES = 1;
private static final int CONSUMIDORES = 1;

private static BufferSinc buffer;

private static void sleepRandom(long max) throws InterruptedException {
	Thread.sleep((long) (Math.random() * max));		
}

public static void productor() {
	try {
		for (int i = 0; i < 20; i++) {
			sleepRandom(500);
			System.out.println("Producido:"+i);
			buffer.insertar(i);
		}
	} catch (InterruptedException e) {
	}
}

public static void consumidor() {
	try {
		while (true) {
			int data = buffer.sacar();
			sleepRandom(1000);
			System.out.println("Consumido:"+data);
		}
	} catch (InterruptedException e) {
	}
}

public static void main(String[] args) {

	buffer = new BufferSinc();

	for (int i = 0; i < PRODUCTORES; i++) {
		final int aux = i;
		new Thread("Productor " + aux) {
			public void run() {
				productor();
			}
		}.start();
	}

	for (int i = 0; i < CONSUMIDORES; i++) {
		final int aux = i;
		new Thread("Consumidor " + aux) {
			public void run() {
				consumidor();
			}
		}.start();
	}
}
}
package ejercicio.ejer5_4;

import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class BufferSinc {

private static final int BUFFER_SIZE = 10;

private int[] datos = new int[BUFFER_SIZE];
private int posInser = 0;
private int posSacar = 0;

private int nProductos = 0;

private Lock em = new ReentrantLock();

private Condition esperaHuecos = em.newCondition();
private Condition esperaProductos = em.newCondition();

public void insertar(int dato) throws InterruptedException {

	try {
		em.lock();

		while (nProductos == BUFFER_SIZE) {
			esperaHuecos.await();
		}

		datos[posInser] = dato;
		posInser = (posInser + 1) % datos.length;

		nProductos++;

		esperaProductos.signal();

	} finally {
		em.unlock();
	}
}

public int sacar() throws InterruptedException {

	int dato;

	try {
		em.lock();

		while (nProductos == 0) {
			esperaProductos.await();
		}

		dato = datos[posSacar];
		posSacar = (posSacar + 1) % datos.length;

		nProductos--;

		esperaHuecos.signal();

	} finally {
		em.unlock();
	}

	return dato;
}
}

Usuarios habituales

  • elraro
  • JuAn4k4
  • willy_chaos
  • Turco