4 Sincronización independiente (lock Striping)¶
Sincronización independiente (lock Striping)¶
Cuando usamos la palabra reservada synchronized
para proteger un bloque de código, debemos pasarle como parámetro la referencia a un objeto cuyo cerrojo intrínseco actúa como protector. Normalmente, usaremos como parámetro el propio objeto que está ejecutando el método, por lo que usaremos la palabra reservada this
. Sin embargo, hay ocasiones en las que es conveniente usar como parámetro un objeto independiente creado específicamente para este propósito.
De esta manera conseguiremos restringir lo máximo posible el coste de ejecución asociado a la sincronización, haciendo que un cerrojo intrínseco proteja un único campo, lo que se conoce como fine-grained synchronization (sincronización de grano fino) o lock striping (cinta de cerrojos).
Por ejemplo, supongamos que tenemos en una misma clase dos atributos cuyo acceso debe ser protegido, pero el hecho de que un hilo esté accediendo a uno de los atributos no implica problema alguno sobre que otro hilo esté accediendo al otro atributo. En este caso no deberíamos usar el mismo objeto (por ejemplo this) para proteger ambos atributos, sino que cada atributo debería ser protegido por un objeto distinto, que crearemos expresamente para dicho cometido. Esto permitirá que un hilo pueda acceder al primer atributo mientras que otro hilo acceda al segundo, ya que están protegidos por objetos distintos.
Proyecto MovieTheater¶
En este proyecto desarrollaremos una aplicación que simula el funcionamiento de un cine con dos taquillas y la cola de venta de entradas de cada una de ellas para las salas del cine. El número de butacas de cada sala debe ser sincronizada de manera independiente, para que mientras se esté realizando la venta de entradas a una sala en una taquilla, en la otra taquilla se puedan vender entradas para otra sala.
public class Main {
private static final int BOX_OFFICES = 2;
private static final int MOVIE_THEATER_CAPACITY = 20;
public static void main(String[] args) {
int movieTheaters[] = { MOVIE_THEATER_CAPACITY, MOVIE_THEATER_CAPACITY };
Cinema cinema = new Cinema(movieTheaters, BOX_OFFICES);
Thread boxOffice1Thread = new Thread(new BoxOffice1Queue(cinema, 1),
"Box office 1");
Thread boxOffice2Thread = new Thread(new BoxOffice2Queue(cinema, 2),
"Box office 2");
boxOffice1Thread.start();
boxOffice2Thread.start();
try {
boxOffice1Thread.join();
boxOffice2Thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < movieTheaters.length; i++) {
System.out.printf("Movie theater %d: %d seats available\n", i + 1,
cinema.getAvailableSeats(i));
}
}
}
public class Cinema {
private final int[] movieTheatersCapacity;
private final int boxOffices;
private int[] availableSeats;
private Object[] movieTheatersLock;
public Cinema(int[] movieTheatersCapacity, int boxOffices) {
if (movieTheatersCapacity == null || boxOffices < 1) {
throw new IllegalArgumentException();
}
for (int aMovieTheatersCapacity : movieTheatersCapacity) {
if (aMovieTheatersCapacity < 1) {
throw new IllegalArgumentException();
}
}
this.movieTheatersCapacity = movieTheatersCapacity;
this.boxOffices = boxOffices;
availableSeats = new int[movieTheatersCapacity.length];
movieTheatersLock = new Object[movieTheatersCapacity.length];
for (int i = 0; i < movieTheatersCapacity.length; i++) {
movieTheatersLock[i] = new Object();
}
resetMovieTheaters();
}
private void resetMovieTheaters() {
System.arraycopy(movieTheatersCapacity, 0, availableSeats, 0,
movieTheatersCapacity.length);
}
public boolean buyTickets(int boxOffice, int movieTheater, int tickets)
throws InterruptedException {
if (movieTheater < 0 || movieTheater >= availableSeats.length || tickets < 1 ||
boxOffice < 1 || boxOffice > boxOffices) {
throw new IllegalArgumentException();
}
synchronized (movieTheatersLock[movieTheater]) {
if (tickets <= availableSeats[movieTheater]) {
availableSeats[movieTheater] -= tickets;
System.out.printf(
"Movie theater #%d: %d tickets bought in box office #%d\n",
movieTheater,
tickets, boxOffice);
Thread.sleep(1000);
return true;
} else {
return false;
}
}
}
public boolean returnTickets(int boxOffice, int movieTheater, int tickets) {
if (movieTheater < 0 || movieTheater >= availableSeats.length || tickets < 1 ||
boxOffice < 1 || boxOffice > boxOffices) {
throw new IllegalArgumentException();
}
synchronized (movieTheatersLock[movieTheater]) {
if (tickets + availableSeats[movieTheater] <=
movieTheatersCapacity[movieTheater]) {
availableSeats[movieTheater] += tickets;
System.out.printf(
"Movie theater %d: %d tickets returned in box office #%d\n",
movieTheater, tickets, boxOffice);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return true;
} else {
return false;
}
}
}
public int getAvailableSeats(int movieTheater) {
return availableSeats[movieTheater];
}
}
public class BoxOffice1Queue implements Runnable {
private final Cinema cinema;
private final int boxOffice;
public BoxOffice1Queue(Cinema cinema, int boxOffice) {
this.cinema = cinema;
this.boxOffice = boxOffice;
}
@Override
public void run() {
try {
cinema.buyTickets(boxOffice, 0, 3);
cinema.buyTickets(boxOffice,0, 2);
cinema.buyTickets(boxOffice,1, 2);
cinema.returnTickets(boxOffice,0, 3);
cinema.buyTickets(boxOffice,0, 5);
cinema.buyTickets(boxOffice,1, 2);
cinema.buyTickets(boxOffice,1, 2);
cinema.buyTickets(boxOffice,1, 2);
} catch (InterruptedException e) {
System.out.println("Box office #%d has been closed");
}
}
}
public class BoxOffice2Queue implements Runnable {
private final Cinema cinema;
private final int boxOffice;
public BoxOffice2Queue(Cinema cinema, int boxOffice) {
this.cinema = cinema;
this.boxOffice = boxOffice;
}
@Override
public void run() {
try {
cinema.buyTickets(boxOffice, 1, 2);
cinema.buyTickets(boxOffice, 1, 4);
cinema.buyTickets(boxOffice, 0, 2);
cinema.buyTickets(boxOffice, 0, 1);
cinema.returnTickets(boxOffice, 1, 2);
cinema.buyTickets(boxOffice, 0, 3);
cinema.buyTickets(boxOffice, 1, 2);
cinema.buyTickets(boxOffice, 0, 2);
} catch (InterruptedException e) {
System.out.printf("Box office #%d has been closed\n", boxOffice);
}
}
}
Si ejecutamos el programa veremos que a la vez se pueden estar comprando entradas para distintas salas del cine.