2011-04-09 45 views
7

Estoy escribiendo una aplicación de subprocesos múltiples que imita una sala de cine. Cada persona involucrada es su propio hilo y la concurrencia debe hacerse completamente mediante semáforos. El único problema que tengo es cómo vincular básicamente los hilos para que puedan comunicarse (a través de un conducto, por ejemplo).Datos de tuberías entre hilos con Java

Por ejemplo:

Cliente [1], que es un hilo, adquiere un semáforo que le permite subir a la taquilla. Ahora el Cliente [1] debe decirle al Agente de Taquilla que quiere ver la película "X". Luego BoxOfficeAgent [1] también es un hilo, debe verificar para asegurarse de que la película no está llena y vender un ticket o decirle al Cliente [1] que elija otra película.

¿Cómo paso esos datos hacia adelante y hacia atrás sin perder la concurrencia con los semáforos?

Además, la única clase que puedo usar de java.util.concurrent es la clase Semaphore.

+1

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. –

Respuesta

7

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.

+0

Esto no satisfará el requisito de la tarea de que "la concurrencia debe hacerse completamente mediante semáforos". (Por supuesto, en la vida real utilizando una de las utilidades de simultaneidad de alto nivel es la mejor opción). –

+0

¡De hecho! Me perdí eso al principio. –

+0

Ya, el único elemento que puedo usar de java.util.concurrent es semáforo. No se pueden usar otras clases de seguridad de subprocesos ... ¿Debería hacerse esto con las tuberías y, de ser así, cómo? – JustinY17

1

Piénselo en términos de memoria compartida con bloqueo de lectura/escritura.

  1. Crea un búfer para colocar el mensaje.
  2. El acceso al búfer debe controlarse utilizando un candado/semáforo.
  3. Utilice este almacenamiento intermedio para fines de comunicación entre hilos.

Saludos

PKV