En el anterior volumen vimos un ejemplo muy completo de uso de monitores en Java. ¿Te lo has perdido? Podéis leerlo aquí: Todo sobre monitores en Java.
En este volumen vamos a hablar sobre semáforos en Java. En el primer volumen sobre concurrencia hablamos sobre qué eran los semáforos en Java, qué era un monitor y también una RC (Región Crítica) y una RCC (Región Crítica Condicional).
A continuación os vamos a poner un ejemplo muy completo sobre semáforos.
Un semáforo sirve para controlar el número de hilos que acceden a la variable compartida, en este caso el libro. Si inicializamos el semáforo a 1 se comportará como un semáforo binario, aunque también lo podemos inicializar por ejemplo a 15, para gestionar adecuadamente los permisos que le queramos otorgar.
Un semáforo puede ser liberado por otro proceso, en los locks deben ser liberados por el mismo proceso.
El problema a resolver es exactamente el mismo que en el volumen anterior: Todo sobre monitores en Java. Es el mismo enunciado pero esta vez lo resolveremos utilizando semáforos, ya veréis que es todo muy similar, los comentarios sobre el código están en el propio código.
Lector.java
[java]package semaforos;
/**
*
* @author Bron
*/
public class Lector extends Thread {
/*
* La clase lector se encargará de leer el libro, varios lectores pueden
* leer el mismo libro simultáneamente.
*/
private int identLector;
private Libro libro;
private Gestion gestion;
public Lector(int identLector, Libro libro, Gestion gestion) {
/**
* Constructor de la clase lectores con atributos identificador, y las
* clases libro y gestion.
*/
this.identLector = identLector;
this.libro = libro;
this.gestion = gestion;
}
private void Esperar(int min, int max) {
/*
* Nos encargamos de proporcionar un tiempo aleatorio, creamos esta
* función por comodidad y porque ya estaba en ejercicios anteriores.
*/
try {
sleep(min + (int) (max * Math.random()));
} catch (Exception e) {
}
}
@Override
public void run() {
/*
* El método run se ejecutará mientras que el libro no esté terminado.
* La funcionalidad es la que se pide en el enunciado, con cada acción
* llamamos a gestion.parar por si hemos pulsado el botón de pausa.
*/
while (libro.libroTerminado() == false) {
gestion.parar();
Esperar(1000, 1000);
gestion.parar();
libro.leerLibro(identLector);
gestion.parar();
Esperar(500, 1000);
gestion.parar();
libro.terminarLeer(identLector);
}
}
}
[/java]
Escritor.java
[java]package semaforos;
/**
*
* @author Bron
*/
public class Escritor extends Thread {
/**
* La clase escritor sólo podrá escribir el libro de 1 en 1 ya que estamos
* en una región crítica con variables compartidas tal y como pone el
* enunciado.
*/
private int identEscritor;
private Libro libro;
private Gestion gestion;
public Escritor(int identEscritor, Libro libro, Gestion gestion) {
/**
* Constructor de la clase Escritor, tenemos los atributos identificador
* del escritor y luego le pasamos el libro y la gestión del libro por
* si tenemos que parar
*/
this.identEscritor = identEscritor;
this.libro = libro;
this.gestion = gestion;
}
private void Esperar(int min, int max) {
/*
* Nos encargamos de proporcionar un tiempo aleatorio, creamos esta
* función por comodidad y porque ya estaba en ejercicios anteriores.
*/
try {
sleep(min + (int) (max * Math.random()));
} catch (Exception e) {
}
}
@Override
public void run() {
/**
* Aquí tenemos todas las acciones que se piden en el enunciado con
* respecto a los escritores.
*/
for (int i = 0; i
gestion.parar();
Esperar(1000, 1000);
gestion.parar();
libro.escribirLibro(identEscritor);
gestion.parar();
Esperar(100, 0);
libro.terminarEscribir(identEscritor);
}
}
}
[/java]
Libro.java
[java] package semaforos;
import java.util.concurrent.Semaphore;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
* @author Bron
*/
public class Libro {
/**
* Esta clase libro es la variable compartida y por tanto, la que tenemos
* que proteger de lectores/escritores para que el programa funcione
* satisfactoriamente.
*
* Creamos todos los atributos necesarios para que el programa funcione,
* incluyendo los semáforos para gestionar la entrada y salida de los escritores
*/
private String libro;
private Interfaz interfaz;
private int lecturas;
private Semaphore semaforoA;
/**
*
* @param interfaz
*/
public Libro(Interfaz interfaz, Semaphore semaforo) {
/**
* Creamos el constructor y inicializamos a lo que queremos.
*/
this.interfaz = interfaz;
libro = "";
lecturas = 0;
semaforoA = semaforo;
}
public void leerLibro(int identificador) {
/*
* Adquirimos sólo un semáforo porque pueden leer simultáneamente varios
* lectores
*/
try {
semaforoA.acquire();
} catch (InterruptedException ex) {
Logger.getLogger(Libro.class.getName()).log(Level.SEVERE, null, ex);
}
interfaz.meterDatos(1, interfaz.leerDatos(1) + "" + identificador + " ");
lecturas++;
/*
* Liberamos a los semáforos.
*/
}
public boolean libroTerminado() {
/**
* Si el libro ha llegado a 50 habremos terminado de leer y devolvemos
* un valor booleano.
*/
if (libro.length() == 50) {
return true;
} else {
return false;
}
}
public void terminarLeer(int identificador) {
/**
* Este método se encarga de terminar de leer los datos, libera el
* semaforo de lectura cuando hemos terminado.
*/
interfaz.meterDatos(1, interfaz.leerDatos(1).replaceAll("" + identificador + " ", ""));
if (libroTerminado()) {
interfaz.meterDatos(4, interfaz.leerDatos(4) + "Leido por " + identificador + ": " + libro + "n");
}
semaforoA.release();
}
public void escribirLibro(int identificador) {
/**
* Este método se encarga de escribir en el libro, la estructura es como
* la de lectura. Adquirimos el semáforo entero porque mientras se
* escribe no se puede leer.
*/
try {
semaforoA.acquire(15);
} catch (InterruptedException ex) {
Logger.getLogger(Libro.class.getName()).log(Level.SEVERE, null, ex);
}
libro = libro + identificador;
interfaz.meterDatos(2, interfaz.leerDatos(2) + identificador);
interfaz.meterDatos(3, libro);
/*
* Liberamos el semáforo.
*/
}
public void terminarEscribir(int identificador) {
/**
* Método que termina de escribir un libro. Liberamos el semáforo de
* escritura.
*/
interfaz.meterDatos(2, interfaz.leerDatos(2).replaceAll("" + identificador, ""));
semaforoA.release(15);
}
}
[/java]
Gestion,java
[java] package semaforos;
import java.util.concurrent.Semaphore;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
* @author Bron
*/
public class Gestion {
/**
* Esta clase es la que gestiona los botones de reanudar y parar.
*/
private Semaphore semaforo;
public boolean pausar;
public Gestion() {
semaforo = new Semaphore(1, true);
}
public void reanudar() {
/*
* Si pulsamos el botón reanudar pondremos pausar a falso y el programa
* continuará, liberamos el semáforo.
*/
pausar = false;
semaforo.release();
}
public void detener() {
/**
* Si pulsamos el botón detener, pondremos pausar a true y activaremos
* el semáforo.
*/
try {
semaforo.acquire();
pausar = true;
} catch (InterruptedException ex) {
Logger.getLogger(Gestion.class.getName()).log(Level.SEVERE, null, ex);
}
}
public void parar() {
/*
* Analizamos la condicion de pausar. Si es true activamos el semáforo y
* a continuación lo liberamos..
*/
if (pausar) {
try {
semaforo.acquire();
} catch (InterruptedException ex) {
Logger.getLogger(Gestion.class.getName()).log(Level.SEVERE, null, ex);
}
semaforo.release();
}
}
}
[/java]
Interfaz.java
[java] package semaforos;
import java.util.concurrent.Semaphore;
/**
*
* @author Bron
*/
public class Interfaz extends javax.swing.JFrame {
/**
* Creates new form Interfaz
*/
public Interfaz() {
initComponents();
}
/**
* Creamos un nuevo objeto Gestion para reanudar y parar el sistema.
*/
public Gestion gestion = new Gestion();
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
//
private void initComponents() {
jLabel1 = new javax.swing.JLabel();
jLabel2 = new javax.swing.JLabel();
jLabel3 = new javax.swing.JLabel();
jLabel4 = new javax.swing.JLabel();
jTextField1 = new javax.swing.JTextField();
jTextField2 = new javax.swing.JTextField();
jTextField3 = new javax.swing.JTextField();
jLabel5 = new javax.swing.JLabel();
jScrollPane1 = new javax.swing.JScrollPane();
jTextArea1 = new javax.swing.JTextArea();
detener = new javax.swing.JButton();
reanudar = new javax.swing.JButton();
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
setTitle("PEC2L : Lectores y Escritores de Libros");
setResizable(false);
jLabel1.setFont(new java.awt.Font("Verdana", 1, 12)); // NOI18N
jLabel1.setText("PEC2L : Lectores y Escritores de Libros");
jLabel2.setFont(new java.awt.Font("Verdana", 1, 12)); // NOI18N
jLabel2.setText("Lectores que actualmente están leyendo el Libro:");
jLabel3.setFont(new java.awt.Font("Verdana", 1, 12)); // NOI18N
jLabel3.setText("Escritores que actualmente están escribiendo el Libro:");
jLabel4.setFont(new java.awt.Font("Verdana", 1, 12)); // NOI18N
jLabel4.setText("Contenido del Libro:");
jTextField1.setFont(new java.awt.Font("Verdana", 1, 12)); // NOI18N
jTextField2.setFont(new java.awt.Font("Verdana", 1, 12)); // NOI18N
jTextField2.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
jTextField2ActionPerformed(evt);
}
});
jTextField3.setFont(new java.awt.Font("Verdana", 1, 12)); // NOI18N
jLabel5.setFont(new java.awt.Font("Verdana", 1, 12)); // NOI18N
jLabel5.setText("Libro terminado:");
jTextArea1.setColumns(20);
jTextArea1.setRows(5);
jScrollPane1.setViewportView(jTextArea1);
detener.setFont(new java.awt.Font("Verdana", 1, 12)); // NOI18N
detener.setText("DETENER");
detener.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
detenerActionPerformed(evt);
}
});
reanudar.setFont(new java.awt.Font("Verdana", 1, 12)); // NOI18N
reanudar.setText("REANUDAR");
reanudar.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
reanudarActionPerformed(evt);
}
});
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
getContentPane().setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addGap(25, 25, 25)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
.addComponent(jLabel5, javax.swing.GroupLayout.PREFERRED_SIZE, 144, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(jTextField3, javax.swing.GroupLayout.DEFAULT_SIZE, 662, Short.MAX_VALUE)
.addComponent(jTextField2, javax.swing.GroupLayout.DEFAULT_SIZE, 662, Short.MAX_VALUE)
.addComponent(jLabel2, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(jLabel3, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(jLabel4, javax.swing.GroupLayout.PREFERRED_SIZE, 144, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(jTextField1)
.addComponent(jScrollPane1)))
.addGroup(layout.createSequentialGroup()
.addGap(221, 221, 221)
.addComponent(jLabel1)))
.addContainerGap())
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
.addGap(0, 169, Short.MAX_VALUE)
.addComponent(reanudar, javax.swing.GroupLayout.PREFERRED_SIZE, 123, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(76, 76, 76)
.addComponent(detener, javax.swing.GroupLayout.PREFERRED_SIZE, 123, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(223, 223, 223))
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addGap(4, 4, 4)
.addComponent(jLabel1, javax.swing.GroupLayout.PREFERRED_SIZE, 29, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(18, 18, 18)
.addComponent(jLabel2)
.addGap(18, 18, 18)
.addComponent(jTextField1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(28, 28, 28)
.addComponent(jLabel3)
.addGap(18, 18, 18)
.addComponent(jTextField2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(27, 27, 27)
.addComponent(jLabel4)
.addGap(27, 27, 27)
.addComponent(jTextField3, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(32, 32, 32)
.addComponent(jLabel5)
.addGap(18, 18, 18)
.addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 131, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 47, Short.MAX_VALUE)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(detener, javax.swing.GroupLayout.PREFERRED_SIZE, 31, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(reanudar, javax.swing.GroupLayout.PREFERRED_SIZE, 31, javax.swing.GroupLayout.PREFERRED_SIZE))
.addGap(20, 20, 20))
);
java.awt.Dimension screenSize = java.awt.Toolkit.getDefaultToolkit().getScreenSize();
setBounds((screenSize.width-730)/2, (screenSize.height-616)/2, 730, 616);
}//
private void reanudarActionPerformed(java.awt.event.ActionEvent evt) {
/*
* En este botón de reanudar, procedemos a habilitar el botón detener y
* a deshabilitar el botón reanudar (que ya ha sido pulsado). A
* continuación, reanudamos el sistema llamando a "gestión"
*/
detener.setEnabled(true);
reanudar.setEnabled(false);
gestion.reanudar();
}
private void detenerActionPerformed(java.awt.event.ActionEvent evt) {
/*
* En este botón de reanudar, procedemos a habilitar el botón detener y
* a deshabilitar el botón reanudar (que ya ha sido pulsado). A
* continuación, reanudamos el sistema llamando a "gestión"
*/
reanudar.setEnabled(true);
detener.setEnabled(false);
gestion.detener();
}
private void jTextField2ActionPerformed(java.awt.event.ActionEvent evt) {
// TODO add your handling code here:
}
// Variables declaration – do not modify
public javax.swing.JButton detener;
private javax.swing.JLabel jLabel1;
private javax.swing.JLabel jLabel2;
private javax.swing.JLabel jLabel3;
private javax.swing.JLabel jLabel4;
private javax.swing.JLabel jLabel5;
private javax.swing.JScrollPane jScrollPane1;
private javax.swing.JTextArea jTextArea1;
private javax.swing.JTextField jTextField1;
private javax.swing.JTextField jTextField2;
private javax.swing.JTextField jTextField3;
public javax.swing.JButton reanudar;
// End of variables declaration
public static void main(String args[]) {
/*
* Programa principal y el que ejecuta todos los hilos concurrentemente.
*/
java.awt.EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
/*
* Creamos nuevos objetos de la interfaz y del libro y ajustamos la interfaz
*
* Creamos el semáforo y se lo pasamos a libro
*/
Interfaz interfaz = new Interfaz();
interfaz.setVisible(true);
interfaz.reanudar.setEnabled(false);
Semaphore semaforo = new Semaphore (15, true);
Libro libro = new Libro(interfaz, semaforo );
/*
* Creamos los hilos con los datos pedidos
*/
for (int i = 20; i
Lector l1 = new Lector(i, libro, interfaz.gestion);
l1.start();
}
for (int i = 0; i
Escritor e1 = new Escritor(i, libro, interfaz.gestion);
e1.start();
}
}
});
}
public void meterDatos(int i, String datos) {
/*
* Esta clase introduce los datos en el jTextField, recibimos un número
* de otra clase para saber dónde debemos introducir la información.
* Gracias al switch podremos introducir fácilmente los datos en el
* jTextField correcto. Al ser void, no devuelve nada.
*/
switch (i) {
case 1: {
jTextField1.setText(datos);
break;
}
case 2: {
jTextField2.setText(datos);
break;
}
case 3: {
jTextField3.setText(datos);
break;
}
case 4: {
jTextArea1.setText(datos);
break;
}
}
}
public String leerDatos(int i) {
/*
* Esta clase devuelve un String con el contenido del jTextField.
* Dependiendo del número pasado por parámetro leeremos un jTextField u
* otro. Si no se corresponde con ninguno, devolvemos cadena vacía.
*/
switch (i) {
case 1:
return jTextField1.getText();
case 2:
return jTextField2.getText();
case 3:
return jTextField3.getText();
case 4:
return jTextArea1.getText();
default:
return "";
}
}
}
[/java]
Lo que cambia con respecto a monitores es la variable compartida ya que lo gestionamos todo con semáforos. La clase gestión e interfaz también tienen líneas extra de código para hacer funcionar correctamente los semáforos (pararlo, iniciarlo e inicializarlo).
Para facilitar la ejecución del código con la misma interfaz gráfica que he usado yo (por si queréis copiar la estructura) os subo el paquete (no el proyecto entero):
Descargar paquete semaforos.ZIP
Esperamos que os haya gustado y hayáis aprendido algo. Si tenéis cualquier duda, podéis poner un comentario.
Próximamente volveremos a tratar este mismo problema con LOCKS.