2 июня 2013 г.

Использование защелок (java.util.concurrent.CountDownLatch)

Защелки необходимы тогда, когда необходимо ускорить выполнение последовательных независимых задач за счет их распараллеливания. Раз уж я остановился на строительных примерах, то ситуацией их жизни может служить приготовление бетона.
В моем случае бетон состоит из 4 составляющих: песок, цемент, щебень и вода. Допустим, что все материалы для приготовления бетона находятся на разном расстоянии от емкости, где его приготавливают.
При последовательном выполнении данных операций нам небходима сумма времени на выполнение каждой операции. Если у нас в распоряжении есть 4 работника, то время наполнения емкости, готовой к замесу, становится равно времени выполнения максимальной операции.
Дополнительным условием для начала замеса является полное заполнение емкости.
Рассмотрим на примере кода.

1. Главный класс, где создаются работники и защелка на 4 события. Этот класс ожидает завершения работы всех работников (latch.await())
import com.google.common.base.Stopwatch;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

public class Main {

    public static void main(String[] args) throws InterruptedException {
        Stopwatch sw = new Stopwatch();
        sw.start();
        
        CountDownLatch latch = new CountDownLatch(4);
        
        new Worker(latch,"Sand").start();
        new Worker(latch,"Cement").start();
        new Worker(latch,"Water").start();
        new Worker(latch,"Breakstone").start();
        
        System.out.println("Waiting for all workers");
        latch.await();
        System.out.println("All workers finished. Now we can shake.");
        
        sw.stop();
        System.out.println("Program time: " + sw.elapsed(TimeUnit.MILLISECONDS) + " ms");
    }
}

2. Класс для генерации случайного времени задержки  (время условно определяет удаленность материала от емкости)
public class RandomGenerator {

    public static long getRandom(long min, long max) {
        return min + (long)(Math.random() * (max - min + 1));
    }
}

3.Класс работника, который выполняет задачу и извещает о выполнении
import com.google.common.base.Stopwatch;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

public class Worker extends Thread {

    private CountDownLatch _cdl;
    private String _name;

    public Worker (CountDownLatch cdl, String name) {
        _cdl = cdl;
        _name = name;
    }

    @Override
    public void run() {
        Stopwatch sw = new Stopwatch();
        sw.start();

        System.out.println(_name + " working...");
        try {
            Thread.sleep(RandomGenerator.getRandom(500, 1000));
        } catch (InterruptedException e) {
            e.printStackTrace(System.err);
        }
        _cdl.countDown();

        sw.stop();
        System.out.println(_name + " time: " + sw.elapsed(TimeUnit.MILLISECONDS) + " ms");
    }
}

Результат выполнения программы
Sand working...
Breakstone working...
Waiting for all workers
Water working...
Cement working...
Water time: 720 ms
Sand time: 887 ms
Cement time: 927 ms
Breakstone time: 959 ms
All workers finished. Now we can shake.
Program time: 960 ms

По результатам работы видно, что общее время выполнения программы 960 мс, а при последовательном выполнении мы б получили время, равное сумме: 720 + 887 + 927 + 959 = 3493 мс.
В данном примере использована библиотека guava для подсчета времени выполнения метода.
Примером для параллельного выполнения из мира Java может служить получение данных о клиенте. На входе в программу мы имеем некий userId клиента и из разных источников запрашиваем данные. Например, по http-запросу №1 мы получаем фамилию и имя клиента, запросом в базу данных год рождения и запросом №2 получаем список банковских счетов.

6 комментариев: