Передача данных между потоками в Java

Я пишу многопоточное приложение, которое имитирует кинотеатре. Каждый участник-это свой собственный поток, и параллелизм должен выполняться полностью семафорами. Единственная проблема, с которой я сталкиваюсь, - это как в основном связать потоки, чтобы они могли общаться (например, через трубу).

например:

клиент[1], который является потоком, приобретает семафор, который позволяет ему подойти к кассе. Теперь клиент[1] должен сообщить кассовому агенту, что они хочу посмотреть фильм "Икс". Затем BoxOfficeAgent[1] также поток, должен проверить, чтобы убедиться, что фильм не заполнен, и либо продать билет, либо сказать клиенту[1] Выбрать другой фильм.

Как передать эти данные взад и вперед, сохраняя при этом параллелизм с семафорами?

кроме того, единственный класс, который я могу использовать из java.утиль.параллельным является семафор класса.

2 ответов


один простой способ передачи данных между потоками-использовать реализации интерфейса BlockingQueue<E>, расположенный в пакете java.util.concurrent.

этот интерфейс имеет методы для добавления элементов в коллекцию с различным поведением:

  • add(E): добавляет, Если это возможно, в противном случае бросает exception
  • boolean offer(E): возвращает true, если элемент был добавлен, и false в противном случае
  • boolean offer(E, long, TimeUnit): пытается добавить элемент, ожидая указанное количество времени
  • put(E): блокирует вызывающий поток, пока элемент не будет добавлен

он также определяет методы извлечения элементов с аналогичным поведением:

  • take(): блоки, пока не будет доступен элемент
  • poll(long, TimeUnit): извлекает элемент или возвращает null

реализации я использую наиболее часто бывают:ArrayBlockingQueue, LinkedBlockingQueue и SynchronousQueue.

первый, ArrayBlockingQueue, имеет фиксированный размер, определенный параметром, переданным его конструктору.

второе, LinkedBlockingQueue, имеет площадь три. Он всегда будет принимать любые элементы, то есть offer немедленно вернет true,add никогда не будет бросать исключение.

третий, и мне самый интересный,SynchronousQueue, это точно труба. Вы можете думать об этом как о очереди с размером 0. Он никогда не будет хранить элемент: эта очередь будет принимать только элементы, если есть какой-то другой поток, пытающийся извлечь из него элементы. И наоборот, операция извлечения возвращает элемент только в том случае, если другой поток пытается его толкнуть.

выполнить домашнее задание требование синхронизация выполняется исключительно с семафорами, вы могли бы вдохновиться описанием, которое я дал вам о SynchronousQueue, и написать что-то очень похожее:

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;
  }
}

обратите внимание, что этот класс представляет подобное поведение тому, что я описал о SynchronousQueue.

после того, как методы put(E) вызывается он получает семафор записи, который будет оставлен пустым, так что другой вызов того же метода будет заблокирован в его первой строке. Затем этот метод сохраняет ссылку на передаваемый объект и освобождает семафор чтения. Этот релиз позволит для любого потока, вызывающего take() метод для продолжения.

первый шаг take() метод, естественно, должен получить семафор чтения, чтобы запретить любой другой поток для одновременного извлечения элемента. После того, как элемент был извлечен и сохранен в локальной переменной (упражнение: что произойдет, если эта строка, E e = this.е были удалены?), метод освобождает семафор записи, так что метод put(E) может быть вызван снова любым thread, и возвращает то, что было сохранено в локальной переменной.

в качестве важного замечания заметьте, что ссылка на передаваемый объект хранится в частное поле, и методы take() и put(E) как финал. Это крайне важно, и часто упускается. Если эти методы не являются окончательными (или, что еще хуже, поле не является частным), наследующий класс сможет изменить поведение take() и put(E) нарушение заключить.

наконец, вы можете избежать необходимости объявлять локальную переменную в take() метод с помощью try {} finally {} следующим образом:

class Pipe<E> {
  // ...
  public final E take() {
    try {
      read.acquire();
      return e;
    } finally {
      write.release();
    }
  }
}

вот, суть этого примера если просто показать использование try/finally что остается незамеченным среди неопытных разработчиков. Очевидно, в этом случае, нет никакой реальной выгоды.

О черт, я в основном закончил твою домашнюю работу за тебя. В отместку - и чтобы вы проверили свои знания о семафорах-почему бы вам не вы реализуете некоторые другие методы, определенные контрактом BlockingQueue? Например, вы можете реализовать offer(E) способ и take(E, long, TimeUnit)!

удачи.


подумайте об этом с точки зрения общей памяти с блокировкой чтения/записи.

  1. создайте буфер, чтобы поместить сообщение.
  2. доступ к буферу должен контролироваться с помощью блокировки / семафора.
  3. используйте этот буфер для целей связи между потоками.

в отношении

PKV