Una manera fácil de pasar datos entre hilos es utilizar las implementaciones de la interfaz BlockingQueue<E>
, que se encuentra en el paquete java.util.concurrent
.
Esta interfaz tiene métodos para agregar elementos a la colección con diferentes comportamientos:
add(E)
: añade, si es posible, se lanzaría una excepción
boolean offer(E)
: devuelve true si el elemento ha sido añadido, falso en caso contrario
boolean offer(E, long, TimeUnit)
: intenta agregar el elemento, esperando la cantidad de tiempo especificada
put(E)
: bloquea el hilo de llamada hasta que se haya agregado el elemento
También define métodos para la recuperación de elemento con comportamientos similares:
take()
: bloques hasta que hay un elemento disponible
poll(long, TimeUnit)
: recupera un elemento o devuelve NULL
Las implementaciones Uso con más frecuencia son: ArrayBlockingQueue
, LinkedBlockingQueue
y SynchronousQueue
.
El primero, ArrayBlockingQueue
, tiene un tamaño fijo, definido por un parámetro pasado a su constructor.
El segundo, LinkedBlockingQueue
, tiene un tamaño ilimitado. Siempre aceptará cualquier elemento, es decir, offer
devolverá verdadero inmediatamente, add
nunca arrojará una excepción.
La tercera, y para mí la más interesante, SynchronousQueue
, es exactamente una tubería. Puedes pensar que es una cola con tamaño 0. Nunca mantendrá un elemento: esta cola solo aceptará elementos si hay algún otro hilo tratando de recuperar elementos de él. Por el contrario, una operación de recuperación solo devolverá un elemento si hay otro hilo que intenta empujarlo.
para cumplir con los tarea requisito de sincronización hecho exclusivamente con semáforos, se podía conseguir inspirado por la descripción que te di sobre el SynchronousQueue, y escribir algo bastante similar:
class Pipe<E> {
private E e;
private final Semaphore read = new Semaphore(0);
private final Semaphore write = new Semaphore(1);
public final void put(final E e) {
write.acquire();
this.e = e;
read.release();
}
public final E take() {
read.acquire();
E e = this.e;
write.release();
return e;
}
}
Tenga en cuenta que este la clase presenta un comportamiento similar a lo que describí sobre SynchronousQueue.
Una vez que se llama a los métodos put(E)
, adquiere el semáforo de escritura, que se dejará vacío, de modo que otra llamada al mismo método se bloquearía en su primera línea. Este método almacena una referencia al objeto que se pasa y libera el semáforo de lectura. Esta versión permitirá que continúe cualquier hilo que llame al método take()
.
El primer paso del método take()
es, naturalmente, adquirir el semáforo de lectura, para no permitir que otro hilo recupere el elemento al mismo tiempo. Después de que el elemento ha sido recuperado y guardado en una variable local (ejercicio: ¿qué pasaría si esa línea, E e = this.e, se eliminara?), el método libera el semáforo de escritura, de modo que el método put(E)
puede ser llamado de nuevo por cualquier hilo, y devuelve lo que se ha guardado en la variable local.
Como una observación importante, observamos que la referencia al objeto que está siendo pasado se mantiene en un campo privado, y los métodos take()
y put(E)
son ambos final de. Esto es de suma importancia, y a menudo se pierde. Si estos métodos no fueran definitivos (o peor, el campo no privado), una clase heredera podría alterar el comportamiento de take()
y put(E)
rompiendo el contrato.
Por último, podría evitar la necesidad de declarar una variable local en el método take()
utilizando try {} finally {}
de la siguiente manera:
class Pipe<E> {
// ...
public final E take() {
try {
read.acquire();
return e;
} finally {
write.release();
}
}
}
Aquí, el punto de este ejemplo, si sólo para mostrar un uso de try/finally
que va desapercibido entre los desarrolladores sin experiencia. Obviamente, en este caso, no hay ganancia real.
Oh, maldita sea, he terminado la tarea para ti. En represalia, y para que pruebes tu conocimiento sobre semáforos, ¿por qué no implementas algunos de los otros métodos definidos por el contrato BlockingQueue? Por ejemplo, podría implementar un método offer(E)
y un take(E, long, TimeUnit)
.
Buena suerte.
La pista principal por la que no le daría es conseguir un bloqueo mental con la palabra "tubo". Piensa más en tener una "caja" con la información, y pones una nota en el cuadro para contarle al otro tema cuando hay algo interesante en el cuadro para que lo vea. –