понедельник, 23 мая 2011 г.

Многопоточность в java. Пример

Удобство
Да, многопоточность в яве действительно сделана хорошо. Инструменты удобны и если хочется получить какой-то высокоуровневый инструмент для многопоточной программы, то он существует (а если нет, то один из существующих наверняка лучше того, который хотелось увидеть).
Простой пример:
private static String[] sites = 
    {"www.google.com", "www.ya.ru", "www.yahoo.com"};
//создание пула потоков    
ExecutorService service = Executors.newCachedThreadPool();
List<Future<String>> futures = 
    new ArrayList<Future<String>>();
for (int i = 0; i < sites.length; ++i) {
    //запуск нового потока с параметром из массива
    Future<String> future = 
        service.submit(new ThreadWorker(sites[i]));
    futures.add(future);
    }
//на этом этапе все потоки запущены
System.out.println("threads started");
for (Future<String> future : futures) {
    //забираем результат выполнения потока
    //если поток ещё не завершился, то происходит ожидание
    System.out.println("get result from Future: " + future.get());
}
//сообщаем пулу о том, что закончили пользоваться потоками
service.shutdown();
Подробнее?

Метод submit принимает класс, реализующий интерфейс Callable и возвращает параметризированный типом результата выполнения потока Future. По сути Future - обёртка для результата потока, который можно получить методом get(). При этом если поток уже успел выполниться, то результат возвращается сразу. Если же нет, то происходит ожидание потока, в связи с чем метод get() надо вызывать аккуратно. Например, в представленной версии кода метод вызывается последовательно для каждого потока. Представим ситуацию, что второй поток выполняется 20 секунд, а третий 5. Тогда несмотря на то, что 3ий уже готов предоставить результат, мы будем ещё 15 секунд ждать ответа от потока №2. Чтобы обойти эту ситуацию нужно воспользоваться методом isDone() класса Future, который сообщит о готовности незамедлительно предоставить результат.
Что такое этот Callable?
public class ThreadWorker implements Callable<String> {

    private String url;

    public ThreadWorker(String url) {
        this.url = url;
    }

    public String call() throws Exception {
        //открываем соединение
        URLConnection connection = 
            new URL("http://" + url).openConnection();
        PrintWriter fileWriter = new PrintWriter(url);
        BufferedReader reader = 
            new BufferedReader(
                new InputStreamReader(connection.getInputStream()));
        String inputLine = reader.readLine();
        //записываем все содержимое в файл
        while (inputLine != null) {
            fileWriter.print(inputLine);
            inputLine = reader.readLine();
        }
        fileWriter.close();
        reader.close();
        System.out.println("file created " + url);
        //файл создан! можно вернуть строку с адресом ресурса
        String ret =  url + " " + connection.getContentType();
        return ret;
    }
}
Как видно, ничего сложного. Перекрываемый метод call, который и является новым потоком. Возвращает он тот тип, которым параметризован Callable. В данном случае это String. В этом примере мы лезем на сайт, скачиваем страничку в файл и рапортуем об этом в консоль. Затем возвращает имя ресурса и тип контента.
А результат?
Стоит сказать. что сайты для примера выбраны не абы как:). Если страницы гугла и яндекса занимают в районе 10 Кб, то страница yahoo аж 127, что увеличивает время создания файла, естественно. Но нам это на руку - тем очевиднее будет многопоточность. Вывод, разумеется, разнится от запуска к запуску. Вот у меня, например, такой:
threads started
file created www.ya.ru
file created www.google.com
get result from Future: www.google.com text/html; charset=windows-1251
get result from Future: www.ya.ru text/html; charset=UTF-8
file created www.yahoo.com
get result from Future: www.yahoo.com text/html;charset=utf-8
Здесь видно, что несмотря на то, что гугл стоит первым в массиве, страничка яндекса скачалась быстрее (у меня пинг до яндекса в 2 раза ниже, чем до гугла, думаю, связано прежде всего с этим). Более того: даже ответ с типом контента от этих потоков пришёл и обработался во 2 цикле главной части быстрее, чем создался файл от yahoo.

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

2 комментария: