Ожидание потока во время цикла

1

Мое приложение имеет 1 глобальный драйвер, который отвечает за выполнение низкоуровневой работы. Затем у меня есть 2 потока, оба из которых используют бесконечные циклы, чтобы выполнить некоторую работу. Мой вопрос заключается в том, как разрешить 1 поток использовать драйвер как можно больше, но дает возможность второму потоку использовать его, когда это необходимо.

Чтобы уточнить, код, который у меня есть, выглядит следующим образом:

public class Game {
    private static final Object LOCK = new Object();
    private static final Logger LOGGER = Logger.getLogger(Game.class);

    private WebDriverController controller;

    public Game(WebDriverController controler) {
        this.controller = controller;
    }

    public void startThreadA() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    synchronized (LOCK) {
                        controller.doSomethingA();
                    }
                }
            }
        }).start();
    }

    public void startThreadB() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    ...
                    ...
                    synchronized (LOCK) {
                        controller.doSomethingB();
                    }
                    ...
                    ...
                }
            }
        }).start();
    }
}

Логика состоит в том, чтобы позволить первому потоку выполнить doSomethingA() как можно больше, при этом второй поток doSomethingA() только блокировку для выполнения небольших задач и затем возвращает блокировку в первый поток.

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

public void startThreadA() {
    new Thread(new Runnable() {
        @Override
        public void run() {
            while (true) {
                synchronized (LOCK) {
                    controller.doSomethingA();
                }
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    LOGGER.error(null, e);
                }
            }
        }
    }).start();
}

Это работает точно так, как предполагалось, но это кажется неправильным. Я не доволен ручной паузой после каждой итерации, особенно если второй поток не нуждается в блокировке, поскольку он тратит время.

Что я могу заменить на паузу, чтобы сделать это более эффективным?

  • 0
    Если второй поток выполняет только небольшие задачи, вы также можете перенести работу в первый поток. Это сделает ваш код намного проще и, возможно, более эффективным.
  • 0
    @PeterLawrey Задачи небольшие, но их много. Идея состоит в том, чтобы позволить первому потоку выполняться всякий раз, когда второму потоку не нужен драйвер для своих задач.
Показать ещё 1 комментарий
Теги:
multithreading
concurrency

5 ответов

0
Лучший ответ

Я думаю, вы приближаетесь к этому из-за неправильного направления, так как в вашей текущей настройке 99.999% от времени потока A вызывает для монитора время обработки. Однако, поскольку у меня недостаточно информации о вашей реальной проблеме, вот быстрое решение, использующее ReentrantLock со справедливым расписанием (FIFO):

protected final ReentrantLock lock = new ReentrantLock(true); // fair scheduling

public void functionA() {
  lock.lock();
  try {
    controller.functionA();
  } finally {
    lock.unlock();
  }
}

public void functionB() {
  lock.lock();
  try {
    controller.functionB();
  } finally {
    lock.unlock();
  }
}

Объяснение:

Если Thread A в настоящий момент удерживает блокировку и вызовы Thread B, B гарантированно получит монитор сразу после того, как A отпустит его, даже если A немедленно (до того, как произойдет какой-либо поток), вызывает его снова.

  • 0
    «Поток B гарантированно получит монитор правильно после…» - это просто неправильно. Этот код практически функционально эквивалентен исходной версии OP, без спящего режима, и, скорее всего, точно так же истощит второй поток ресурсов.
  • 0
    Нет это не так. Справедливое планирование гарантирует, что вызовы обрабатываются по порядку, а не полуслучайно, как при синхронизации.
Показать ещё 3 комментария
1

Почему вы используете synchronized в run()? Используйте synchronized или Lock в своих методах в WebDriverController.

public void doSomeThingA(){
   lock.lock();
   try {
      //your stuff
   } finally {
       lock.unlock();
   }
}

И в методе выполнения Thread вызывают эти методы.

  • 0
    Спасибо за Ваш ответ. Будет ли блокировка WebDriverController быть переменной класса, используемой методами класса?
  • 0
    Да. В вашем WebDriverController вы можете использовать одну из реализаций блокировки
Показать ещё 2 комментария
0

Здесь есть несколько вариантов. Лучшая ставка в этом случае, вероятно, будет снимать ответственность за принятие решения о том, когда делать работу из потоков, и вместо этого ждать события от монитора, чтобы освободить потоки для работы. Затем вы можете запланировать работу в зависимости от того, какое отношение лучше всего подходит для этой цели.

В качестве альтернативы устраните недостаток безопасности потока от кода вашего контроллера.

-1

Предполагая, что организация нитей выше всего подходит для вашего конкретного случая, ваша проблема заключается в том, что первая нить удерживает блокировку слишком долго, таким образом, голодая вторая.

Вы можете проверить, действительно ли функция doSomethingA действительно требует блокировки драйвера во время ее выполнения (в большинстве случаев это не так), и если он не разбит на несколько меньших блоков выполнения, некоторые из которых удерживают блокировку, а другие не выполняют, Это создаст больше времени для второго потока, когда он будет нужен.

Если это невозможно сделать, вам действительно нужно переосмыслить свое приложение, потому что вы создали узкое место в ресурсе.

-2

Похоже, Thread.yield() - это то, что вы ищете.

  • 0
    Позвольте мне процитировать из документации, почему это плохая идея: «Планировщик может игнорировать этот намек». и «Редко целесообразно использовать этот метод. Он может быть полезен для целей отладки или тестирования»
  • 0
    Не знаю, как вы интерпретируете этот отрывок как «это плохая идея» ... Возможно, у вас лучше разбираться в английском языке, чем у меня ...
Показать ещё 2 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню