Мое приложение имеет 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();
}
Это работает точно так, как предполагалось, но это кажется неправильным. Я не доволен ручной паузой после каждой итерации, особенно если второй поток не нуждается в блокировке, поскольку он тратит время.
Что я могу заменить на паузу, чтобы сделать это более эффективным?
Я думаю, вы приближаетесь к этому из-за неправильного направления, так как в вашей текущей настройке 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 немедленно (до того, как произойдет какой-либо поток), вызывает его снова.
Почему вы используете synchronized
в run()
? Используйте synchronized
или Lock
в своих методах в WebDriverController
.
public void doSomeThingA(){
lock.lock();
try {
//your stuff
} finally {
lock.unlock();
}
}
И в методе выполнения Thread вызывают эти методы.
WebDriverController
быть переменной класса, используемой методами класса?
Здесь есть несколько вариантов. Лучшая ставка в этом случае, вероятно, будет снимать ответственность за принятие решения о том, когда делать работу из потоков, и вместо этого ждать события от монитора, чтобы освободить потоки для работы. Затем вы можете запланировать работу в зависимости от того, какое отношение лучше всего подходит для этой цели.
В качестве альтернативы устраните недостаток безопасности потока от кода вашего контроллера.
Предполагая, что организация нитей выше всего подходит для вашего конкретного случая, ваша проблема заключается в том, что первая нить удерживает блокировку слишком долго, таким образом, голодая вторая.
Вы можете проверить, действительно ли функция doSomethingA действительно требует блокировки драйвера во время ее выполнения (в большинстве случаев это не так), и если он не разбит на несколько меньших блоков выполнения, некоторые из которых удерживают блокировку, а другие не выполняют, Это создаст больше времени для второго потока, когда он будет нужен.
Если это невозможно сделать, вам действительно нужно переосмыслить свое приложение, потому что вы создали узкое место в ресурсе.
Похоже, Thread.yield()
- это то, что вы ищете.