Правильная синхронизация потоков Java с помощью wait/notifyAll?

63
7

Вот упрощенная версия моего приложения, показывающая, что я делаю.


/*
in my app main():

Runner run = new Runner();

run.dowork();

*/

class Runner
{
private int totalWorkers = 2;
private int workersDone = 0;

public synchronized void workerDone()
{
workersDone++;
notifyAll();
}

public synchronized void dowork()
{
workersDone = 0;

//<code for opening a file here, other setup here, etc>

Worker a = new Worker(this);
Worker b = new Worker(this);

while ((line = reader.readLine()) != null)
{
//<a large amount of processing on 'line'>

a.setData(line);
b.setData(line);

while (workersDone < totalWorkers)
{
wait();
}
}
}
}

class Worker implements Runnable
{
private Runner runner;
private String data;

public Worker(Runner r)
{
this.runner = r;
Thread t = new Thread(this);
t.start();
}

public synchronized void setData(String s)
{
this.data = s;
notifyAll();
}

public void run
{
while (true)
{
synchronized(this)
{
wait();

//<do work with this.data here>

this.runner.workerDone();
}
}
}
}


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


Проблема, с которой я столкнулась, заключается в том, что этот код блокируется. Я читаю файл с более чем 1 миллионом строк, и мне повезло получить 100 строк, прежде чем приложение перестанет отвечать.


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


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


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


Я знаю, что у меня отсутствует довольно простой аспект синхронизации на Java, но я застрял на этом этапе. Если бы кто-то мог объяснить, что я делаю неправильно, я был бы признателен. Я считаю, что не понимаю какой-то аспект синхронизации, но у меня нет идей, пытаясь исправить это.

спросил(а) 2021-01-25T14:59:37+03:00 4 месяца, 4 недели назад
1
Решение
88

Работа непосредственно с synchronized, wait() и notify() определенно сложная.


К счастью, Java Concurrency API предоставляет некоторые превосходные объекты управления для такого рода вещей, которые гораздо более интуитивно понятны. В частности, посмотрите CyclicBarrier и CountDownLatch; один из них почти наверняка будет тем, что вы ищете.


Вы также можете найти ThreadPoolExecutor, чтобы это было удобно для этой ситуации.

Вот простой пример/преобразование вашего фрагмента, который производит следующий вывод (без взаимоблокировки, конечно):


Читать строку: Строка 1
Ожидание завершения работы в режиме онлайн: Линия 1
Работа на линии: Строка 1
Работа на линии: Строка 1
Строка чтения: Строка 2
Ожидание завершения работы в режиме онлайн: Строка 2
Работа на линии: Строка 2
Работа на линии: Строка 2
Строка чтения: Строка 3
Ожидание завершения работы в режиме онлайн: Строка 3
Работа на линии: Строка 3
Работа на линии: Строка 3
Все работы завершены!



public class Runner
{

public static void main(String args[]) {
Runner r = new Runner();
try {
r.dowork();
} catch (IOException e) {
// handle
e.printStackTrace();
}
}

CyclicBarrier barrier;
ExecutorService executor;
private int totalWorkers = 2;

public Runner() {
this.barrier = new CyclicBarrier(this.totalWorkers + 1);
this.executor = Executors.newFixedThreadPool(this.totalWorkers);
}

public synchronized void dowork() throws IOException
{
//<code for opening a file here, other setup here, etc>
//BufferedReader reader = null;
//String line;

final Worker worker = new Worker();

for(String line : new String[]{"Line 1", "Line 2", "Line 3"})
//while ((line = reader.readLine()) != null)
{
System.out.println("Read line: " + line);
//<a large amount of processing on 'line'>

for(int c = 0; c < this.totalWorkers; c++) {
final String curLine = line;
this.executor.submit(new Runnable() {
public void run() {
worker.doWork(curLine);
}
});
}

try {
System.out.println("Waiting for work to be complete on line: " + line);
this.barrier.await();
} catch (InterruptedException e) {
// handle
e.printStackTrace();
} catch (BrokenBarrierException e) {
// handle
e.printStackTrace();
}
}

System.out.println("All work complete!");
}

class Worker
{
public void doWork(String line)
{
//<do work with this.data here>
System.out.println("Working on line: " + line);

try {
Runner.this.barrier.await();
} catch (InterruptedException e) {
// handle
e.printStackTrace();
} catch (BrokenBarrierException e) {
// handle
e.printStackTrace();
}
}
}
}

ответил(а) 2021-01-25T14:59:37+03:00 4 месяца, 4 недели назад
45

ИМХО, вы неправильно разместили "workersDone = 0".

public synchronized void dowork()
{
// workersDone = 0;

//<code for opening a file here, other setup here, etc>

Worker a = new Worker(this);
Worker b = new Worker(this);

while ((line = reader.readLine()) != null)
{
workersDone = 0;

//<a large amount of processing on 'line'>

a.setData(line);
b.setData(line);

while (workersDone < totalWorkers)
{
wait();
}
}
}

ответил(а) 2021-01-25T14:59:37+03:00 4 месяца, 4 недели назад
Ваш ответ
Введите минимум 50 символов
Чтобы , пожалуйста,
Выберите тему жалобы:

Другая проблема