вторник, 19 августа 2014 г.

Пишем простое консольное приложение для скачки картинок с интернета. Используем multithreading.

О чем статья.

Цель данной статьи: написать разные варианты программы для скачивания картинок, чтобы показать множество разных способов создания многопоточных программ в Java. А конкретнее мы будем использовать такие способы:

  • расширение класса Thread;
  • интерфейс Runnable;
  • интерфейс Callable с FutureTask;
  • интерфейс Callable с ExecutorService;
  • интерфейс Callable с CompletionService
  • фреймворк ForkJoin;
Естественно это не все многопоточные фичи, а только некоторые из них. Но я думаю что и это будет полезно для тех кто интересуется написанием быстрых многопоточных программ на  Java. Статья не претендует на полноту и совершенство, поэтому не судите строго первый блин комом ;) 

Пишем код

Для начала создадим класс который будет собственно качать картинку. Назовем его, например, Loader, также создадим свой Exception для пустого url:

import com.easy.imageloader.threads.exception.EmptyURLException;

import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URL;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.file.Path;
import java.nio.file.Paths;

/**
 * Created by Andrienko Alexander on 03.07.2014.
 */
public class Loader {
    private String urlLine = "";

    public String getUrlLine() {
        return urlLine;
    }

    public void setUrlLine(String urlLine) throws EmptyURLException {
        if (urlLine == null || urlLine.length() == 0) {
            throw new EmptyURLException("Link of image is null");
        }
        this.urlLine = urlLine;
    }

    public void load() throws IOException {
        URL url = new URL(urlLine);
        int begin = urlLine.lastIndexOf("/");
        if (begin == -1) {
            System.out.println("index of urlLine = -1");
            return;
        }
        String name = urlLine.substring(begin + 1, urlLine.length());
        Path path2 = Paths.get("images");
        if (!Files.exists(path2)) {
            Files.createDirectories(path2);
        }
        Path downloadPath = Paths.get("images\\" + name);
        ReadableByteChannel rbc = Channels.newChannel(url.openStream());
        FileOutputStream fos = new FileOutputStream(downloadPath.toFile());
        fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
    }
}


/**
 * Created by Andrienko Alexander on 03.07.2014.
 */
public class EmptyURLException extends Exception {
    public EmptyURLException() {
        super();
    }

    public EmptyURLException(String message) {
        super(message);
    }
}


Как видите, есть гетер и сеттер для того чтобы забрать или задать url картинки. Для скачки картинки нужно вызвать метод load. Он использует стандартную библиотеку nio для скачивания. Сначала мы преобразуем строку url в класс URL (инкапсулируем путь и получаем доступ к методам этого полезного класса). Дальше из ссылки "вырезаем" имя файла и сохраняем его в переменную name. Создаем путь path к папке в которой мы будем хранить скачанные картинки. Дальше проверяем существует ли она, если нет то создаем. Как вы наверное уже поняли картинки будут храниться в папке images, которая находиться в корне проекта. Дальше создаем путь по которому будем хранить картинку. Потом создаем канал rbc в который кладем поток байтов извлеченный из path. Дальше формируем поток символов FileOutputStream fos для записи у файл. И напоследок серия эффектных методов:
 fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
То есть мы записываем у файл с помощью потока символов fos байты из канала rbc.

Качаем 11 картинок в один поток. Медленно работает, зато писать однопоточный код проще...


Создадим файл который будет качать картинки. Качаться они будут по одной в главном потоке Main. url мы забьем хардкодом, НЕКРАСИВО, но для примера пойдет:

import com.easy.imageloader.threads.exception.EmptyURLException;
import com.easy.imageloader.threads.loader.Loader;

import java.io.IOException;

/**
 * Created by Andrienko Alexander on 03.07.2014.
 */
public class ApplicationOneThreadImageLoader {
    public static void main(String[] args) throws EmptyURLException, IOException {
        Loader loader = new Loader();
        String[] arrayLink = new String[] {
                "http://upload.wikimedia.org/wikipedia/commons/9/9b/Black_Aston_Martin_DBS_fr.jpg",
                "http://chudesa-mira.ru/uploads/images/00/00/21/2011/11/17/872999.jpg",
                "http://unnatural.ru/wp-content/uploads/2011/04/Bugatti-Veyron-16.4-Super-Sport-1.jpg",
                "http://bom.flenda.ru/images/gallery/97/327_1000x750.jpg",
                "http://bom.flenda.ru/images/gallery/97/322_1000x750.jpg",
                "http://img11.nnm.me/9/5/c/f/0/95cf0985d6a5b0233e01669da5fe77c9_full.jpg",
                "http://img.gazeta.ru/files3/717/3990717/bmw-pic452-452x452-36449.jpg",
                "http://telegraf.com.ua/files/2012/10/bmv_-x6_-sinyaya_avtomobili_mashiny_avto.jpg",
                "http://pro-rap.net/uploads/posts/2013-09/1380105844_331651.jpg",
                "http://original-news.ru/wp-content/uploads/2013/02/Bugatti-Veyron-Super-Sport-%D0%" +
                        "B4%D0%BE%D1%80%D0%BE%D0%B3%D0%BE%D0%B9-%D0%B0%D0%B2%D1%82%D0%BE%D0%BC%D0%BE" +
                        "%D0%B1%D0%B8%D0%BB%D1%8C.jpg",
                "http://neobychno.com/img/2011/11/1-bugatti-veyron1-revise1.jpg"
        };
        for (String link: arrayLink) {
            loader.setUrlLine(link);
            loader.load();
        }
    }
}
Тут все просто: создаем массив из 11 картинок, потом в цикле их качаем по одной. Этот пример я написал исключительно для сравнения. Однопоточные программы работают медленнее, чем многопоточные, но писать их легче, и всегда есть участки кода которые не поддаются эффективному розпаралеливанию, или затрата на создания потоков не оправдана (выигрыша в скорости нет, или эта часть кода должна работать последовательно).

Вариант 1 Розширяем класс Thread


Тут все просто расширяем класс Thread в котором нужно обязательно реализовать метод run();

public class ThreadImageLoader extends Thread{
    String url;
    public ThreadImageLoader(String url) {
        super();
        this.url = url;
        start();
    }

    @Override
    public void run() {
        Loader loader = new Loader();
        try {
            loader.setUrlLine(url);
            loader.load();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

public class ApplicationThread {
    public static void main(String[] args) {
        ThreadImageLoader thread1 = new ThreadImageLoader("http://chudesa-mira.ru/uploads/images/00/00/21/2011/11/17/872999.jpg");
        ThreadImageLoader thread2 = new ThreadImageLoader("http://unnatural.ru/wp-content/uploads/2011/04/Bugatti-Veyron-16.4-Super-Sport-1.jpg");
        ThreadImageLoader thread3 = new ThreadImageLoader("http://bom.flenda.ru/images/gallery/97/327_1000x750.jpg");
        ThreadImageLoader thread4 = new ThreadImageLoader("http://bom.flenda.ru/images/gallery/97/322_1000x750.jpg");
        ThreadImageLoader thread5 = new ThreadImageLoader("http://img11.nnm.me/9/5/c/f/0/95cf0985d6a5b0233e01669da5fe77c9_full.jpg");
        ThreadImageLoader thread6 = new ThreadImageLoader("http://img.gazeta.ru/files3/717/3990717/bmw-pic452-452x452-36449.jpg");
        ThreadImageLoader thread7 = new ThreadImageLoader("http://telegraf.com.ua/files/2012/10/bmv_-x6_-sinyaya_avtomobili_mashiny_avto.jpg");
        ThreadImageLoader thread8 = new ThreadImageLoader("http://pro-rap.net/uploads/posts/2013-09/1380105844_331651.jpg");
        ThreadImageLoader thread9 = new ThreadImageLoader("http://original-news.ru/wp-content/uploads/2013/02/Bugatti-Veyron-Super-Sport-%D0%B4%D0%BE%D1%80%D0%BE%D0%B3%D0%BE%D0%B9-%D0%B0%D0%B2%D1%82%D0%BE%D0%BC%D0%BE%D0%B1%D0%B8%D0%BB%D1%8C.jpg");
        ThreadImageLoader thread10 = new ThreadImageLoader("http://neobychno.com/img/2011/11/1-bugatti-veyron1-revise1.jpg");
    }
}

Поток запускается методом start(). Если запустить метод run, то код будет выполняться, но не в отдельном потоке, а в главном потоке, то есть ни о какой мультипоточности речь не будет идти. Код отработает последовательно. Кстати метод start() не обязательно запускать в конструкторе, это можно сделать и в точке вызова потока. Есть разные вариации создания потока через расширения класса Thread:  анонимные классы в методах или конструкторах, и сделать внутренний класс который расширит Thread и т.д.

Вариант 2 Реализовываем  интерфейс Runnable


Будем реализовывать интерфейс Runnable. Если вы посмотрите в конструктор то заметите что мы там создаем новый Thread поток в который через this кладем ссылку на текущий RunnableImageLoader. А дальше методом start() запускаем поток. Таким образом как-только вы создали экземпляр класса RunnableImageLoader (как говорят программисты создали инстанс) поток сразу инициализируется и запуститься на выполнение.

Вариант 2.1

/**
 * Created by Andrienko Alexander on 05.07.2014.
 */
public class RunnableImageLoader implements Runnable {
    String url;
    Thread thread;
    int i;
    public RunnableImageLoader(int i, String url) {
        thread = new Thread(this);
        this.url = url;
        thread.start();
        this.i = i;
    }

    @Override
    public void run() {
        Loader loader = new Loader();
        try {
            loader.setUrlLine(url);
            loader.load();
            System.out.println(i);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

/**
 * Created by Andrienko Alexander on 17.08.2014.
 */
public class ApplicationRunnable {
    public static void main(String[] args) {
        RunnableImageLoader thread1 = new RunnableImageLoader(1, "http://chudesa-mira.ru/uploads/images/00/00/21/2011/11/17/872999.jpg");
        RunnableImageLoader thread2 = new RunnableImageLoader(2, "http://unnatural.ru/wp-content/uploads/2011/04/Bugatti-Veyron-16.4-Super-Sport-1.jpg");
        RunnableImageLoader thread3 = new RunnableImageLoader(3, "http://bom.flenda.ru/images/gallery/97/327_1000x750.jpg");
        RunnableImageLoader thread4 = new RunnableImageLoader(4, "http://bom.flenda.ru/images/gallery/97/322_1000x750.jpg");
        RunnableImageLoader thread5 = new RunnableImageLoader(5, "http://img11.nnm.me/9/5/c/f/0/95cf0985d6a5b0233e01669da5fe77c9_full.jpg");
        RunnableImageLoader thread6 = new RunnableImageLoader(6, "http://img.gazeta.ru/files3/717/3990717/bmw-pic452-452x452-36449.jpg");
        RunnableImageLoader thread7 = new RunnableImageLoader(7, "http://telegraf.com.ua/files/2012/10/bmv_-x6_-sinyaya_avtomobili_mashiny_avto.jpg");
        RunnableImageLoader thread8 = new RunnableImageLoader(8, "http://pro-rap.net/uploads/posts/2013-09/1380105844_331651.jpg");
        RunnableImageLoader thread9 = new RunnableImageLoader(9, "http://original-news.ru/wp-content/uploads/2013/02/Bugatti-Veyron-Super-Sport-%D0%B4%D0%BE%D1%80%D0%BE%D0%B3%D0%BE%D0%B9-%D0%B0%D0%B2%D1%82%D0%BE%D0%BC%D0%BE%D0%B1%D0%B8%D0%BB%D1%8C.jpg");
        RunnableImageLoader thread10 = new RunnableImageLoader(10,"http://neobychno.com/img/2011/11/1-bugatti-veyron1-revise1.jpg");
    }
}

Вариант 2.2

/**
 * Created by Andrienko Alexander on 17.08.2014.
 */
public class RunnableImageLoader2 implements Runnable{
    String url;

    public RunnableImageLoader2(String url){
        this.url = url;
    }
    @Override
    public void run() {
        Loader loader = new Loader();
        try {
            loader.setUrlLine(url);
            loader.load();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}


Вариант 2.3

/**
 * Created by Andrienko Alexander on 17.08.2014.
 */
public class RunnableImageLoader3 implements Runnable{
    String url;
    Thread thread;
    public RunnableImageLoader3(String url) {
        thread = new Thread(this);
        this.url = url;
    }

    public void start() {
        thread.start();
    }

    @Override
    public void run() {
        Loader loader = new Loader();
        try {
            loader.setUrlLine(url);
            loader.load();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

/**
 * Created by Andrienko Alexander on 17.08.2014.
 */
public class ApplicationRunnable3 {
    public static void main(String[] args) {
        RunnableImageLoader3 thread1 = new RunnableImageLoader3("http://chudesa-mira.ru/uploads/images/00/00/21/2011/11/17/872999.jpg");
        thread1.start();
        RunnableImageLoader3 thread2 = new RunnableImageLoader3("http://unnatural.ru/wp-content/uploads/2011/04/Bugatti-Veyron-16.4-Super-Sport-1.jpg");
        thread2.start();
        RunnableImageLoader3 thread3 = new RunnableImageLoader3("http://bom.flenda.ru/images/gallery/97/327_1000x750.jpg");
        thread3.start();
        RunnableImageLoader3 thread4 = new RunnableImageLoader3("http://bom.flenda.ru/images/gallery/97/322_1000x750.jpg");
        thread4.start();
        RunnableImageLoader3 thread5 = new RunnableImageLoader3("http://img11.nnm.me/9/5/c/f/0/95cf0985d6a5b0233e01669da5fe77c9_full.jpg");
        thread5.start();
        RunnableImageLoader3 thread6 = new RunnableImageLoader3("http://img.gazeta.ru/files3/717/3990717/bmw-pic452-452x452-36449.jpg");
        thread6.start();
        RunnableImageLoader3 thread7 = new RunnableImageLoader3("http://telegraf.com.ua/files/2012/10/bmv_-x6_-sinyaya_avtomobili_mashiny_avto.jpg");
        thread7.start();
        RunnableImageLoader3 thread8 = new RunnableImageLoader3("http://pro-rap.net/uploads/posts/2013-09/1380105844_331651.jpg");
        thread8.start();
        RunnableImageLoader3 thread9 = new RunnableImageLoader3("http://original-news.ru/wp-content/uploads/2013/02/Bugatti-Veyron-Super-Sport-%D0%B4%D0%BE%D1%80%D0%BE%D0%B3%D0%BE%D0%B9-%D0%B0%D0%B2%D1%82%D0%BE%D0%BC%D0%BE%D0%B1%D0%B8%D0%BB%D1%8C.jpg");
        thread9.start();
        RunnableImageLoader3 thread10 = new RunnableImageLoader3("http://neobychno.com/img/2011/11/1-bugatti-veyron1-revise1.jpg");
        thread10.start();
    }
}

Вариант 3 Реализовываем интерфейс Сallable


Callable это тоже интерфейс но в отличие от Runnable, он может возвращать результат своей роботы и не поглощает Exception. Под поглощение нужно понимать что в Runnable мы не можем выбросить Exception из метода run(), мы должны внутри его обработать с помощью try-catch. Код Callable приведу одни раз но дам сразу несколько способов как его можно запустить. 

/**
 * Created by Andrienko Alexander on 17.08.2014.
 */
public class CallableImageLoader implements Callable<String> {
    private String url;
    private int i;

    public CallableImageLoader(int i, String url) {
        this.url = url;
        this.i = i;
    }

    @Override
    public String call() throws Exception {
        Loader loader = new Loader();
        loader.setUrlLine(url);
        loader.load();
        System.out.println(i);
        return "Loading image complete" + i;
    }
}

Можно запустить используя обертку FutureTask.

/**
 * Created by Andrienko Alexander on 17.08.2014.
 */
public class ApplicationCallableWithFutureTask {
    public static void main(String[] args) {
        List<FutureTask<String>> futureTasks = new LinkedList<FutureTask<String>>(
            Arrays.asList(
                new FutureTask<String>(new CallableImageLoader(1,"http://chudesa-mira.ru/uploads/images/00/00/21/2011/11/17/872999.jpg")),
                new FutureTask<String>(new CallableImageLoader(2, "http://unnatural.ru/wp-content/uploads/2011/04/Bugatti-Veyron-16.4-Super-Sport-1.jpg")),
                new FutureTask<String>(new CallableImageLoader(3, "http://bom.flenda.ru/images/gallery/97/327_1000x750.jpg")),
                new FutureTask<String>(new CallableImageLoader(4, "http://bom.flenda.ru/images/gallery/97/322_1000x750.jpg")),
                new FutureTask<String>(new CallableImageLoader(5, "http://img11.nnm.me/9/5/c/f/0/95cf0985d6a5b0233e01669da5fe77c9_full.jpg")),
                new FutureTask<String>(new CallableImageLoader(6, "http://img.gazeta.ru/files3/717/3990717/bmw-pic452-452x452-36449.jpg")),
                new FutureTask<String>(new CallableImageLoader(7, "http://telegraf.com.ua/files/2012/10/bmv_-x6_-sinyaya_avtomobili_mashiny_avto.jpg")),
                new FutureTask<String>(new CallableImageLoader(8, "http://pro-rap.net/uploads/posts/2013-09/1380105844_331651.jpg")),
                new FutureTask<String>(new CallableImageLoader(9, "http://original-news.ru/wp-content/uploads/2013/02/Bugatti-Veyron-Super-Sport-%D0%B4%D0%BE%D1%80%D0%BE%D0%B3%D0%BE%D0%B9-%D0%B0%D0%B2%D1%82%D0%BE%D0%BC%D0%BE%D0%B1%D0%B8%D0%BB%D1%8C.jpg")),
                new FutureTask<String>(new CallableImageLoader(10, "http://neobychno.com/img/2011/11/1-bugatti-veyron1-revise1.jpg"))
            )
        );
        for(FutureTask<String> task: futureTasks) {
            new Thread(task).start();
        }
        for (FutureTask<String> task: futureTasks) {
            try {
                System.out.println(task.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
    }
}

Можно запустить через CompletionService.

/**
 * Created by USER on 19.08.2014.
 */
public class ApplicationCompletionService {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService service = Executors.newFixedThreadPool(10);
        List<CallableImageLoader> listThreads = new ArrayList<CallableImageLoader>(
                Arrays.asList(
                    new CallableImageLoader(1, "http://unnatural.ru/wp-content/uploads/2011/04/Bugatti-Veyron-16.4-Super-Sport-1.jpg"),
                    new CallableImageLoader(2, "http://bom.flenda.ru/images/gallery/97/327_1000x750.jpg"),
                    new CallableImageLoader(3, "http://bom.flenda.ru/images/gallery/97/322_1000x750.jpg"),
                    new CallableImageLoader(4, "http://img11.nnm.me/9/5/c/f/0/95cf0985d6a5b0233e01669da5fe77c9_full.jpg"),
                    new CallableImageLoader(5, "http://img.gazeta.ru/files3/717/3990717/bmw-pic452-452x452-36449.jpg"),
                    new CallableImageLoader(6, "http://telegraf.com.ua/files/2012/10/bmv_-x6_-sinyaya_avtomobili_mashiny_avto.jpg"),
                    new CallableImageLoader(7, "http://pro-rap.net/uploads/posts/2013-09/1380105844_331651.jpg"),
                    new CallableImageLoader(8, "http://original-news.ru/wp-content/uploads/2013/02/Bugatti-Veyron-Super-Sport-%D0%B4%D0%BE%D1%80%D0%BE%D0%B3%D0%BE%D0%B9-%D0%B0%D0%B2%D1%82%D0%BE%D0%BC%D0%BE%D0%B1%D0%B8%D0%BB%D1%8C.jpg"),
                    new CallableImageLoader(9, "http://chudesa-mira.ru/uploads/images/00/00/21/2011/11/17/872999.jpg"),
                    new CallableImageLoader(10, "http://neobychno.com/img/2011/11/1-bugatti-veyron1-revise1.jpg")
                )
        );
        CompletionService<String> completionService = new ExecutorCompletionService(service);
        for (CallableImageLoader elem: listThreads) {
            if (elem instanceof Callable) {
                completionService.submit(elem);
            }
        }
        for (int i = 0; i < 10; i++) {
            try {
                String result = completionService.take().get().toString();
                System.out.println("result is " + result);
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
        service.shutdown();
    }
}

Можно через ExecutorService:

/**
 * Created by Andrienko Alexander on 09.07.2014.
 */
public class ApplicationCallable {
    public static void main(String[] args) {
        ExecutorService es = Executors.newFixedThreadPool(10);
        List<Future> listFutures = new LinkedList<>();
        listFutures.add(es.submit(new CallableImageLoader(1, "http://chudesa-mira.ru/uploads/images/00/00/21/2011/11/17/872999.jpg")));
        listFutures.add(es.submit(new CallableImageLoader(2, "http://unnatural.ru/wp-content/uploads/2011/04/Bugatti-Veyron-16.4-Super-Sport-1.jpg")));
        listFutures.add(es.submit(new CallableImageLoader(3, "http://bom.flenda.ru/images/gallery/97/327_1000x750.jpg")));
        listFutures.add(es.submit(new CallableImageLoader(4, "http://bom.flenda.ru/images/gallery/97/322_1000x750.jpg")));
        listFutures.add(es.submit(new CallableImageLoader(5, "http://img11.nnm.me/9/5/c/f/0/95cf0985d6a5b0233e01669da5fe77c9_full.jpg")));
        listFutures.add(es.submit(new CallableImageLoader(6, "http://img.gazeta.ru/files3/717/3990717/bmw-pic452-452x452-36449.jpg")));
        listFutures.add(es.submit(new CallableImageLoader(7, "http://telegraf.com.ua/files/2012/10/bmv_-x6_-sinyaya_avtomobili_mashiny_avto.jpg")));
        listFutures.add(es.submit(new CallableImageLoader(8, "http://pro-rap.net/uploads/posts/2013-09/1380105844_331651.jpg")));
        listFutures.add(es.submit(new CallableImageLoader(9, "http://original-news.ru/wp-content/uploads/2013/02/Bugatti-Veyron-Super-Sport-%D0%B4%D0%BE%D1%80%D0%BE%D0%B3%D0%BE%D0%B9-%D0%B0%D0%B2%D1%82%D0%BE%D0%BC%D0%BE%D0%B1%D0%B8%D0%BB%D1%8C.jpg")));
        listFutures.add(es.submit(new CallableImageLoader(10, "http://neobychno.com/img/2011/11/1-bugatti-veyron1-revise1.jpg")));
        for (Future future: listFutures) {
            try {
                System.out.println(future.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
        es.shutdown();
    }
}

и снова же через ExecutorService  но не через newFixedThreadPool а через newCachedThreadPool :

/**
 * Created by Andrienko Alexander on 09.07.2014.
 */
public class ApplicationCallable {
    public static void main(String[] args) {
        ExecutorService es = Executors.newCachedThreadPool();
        List<Future> listFutures = new LinkedList<>();
        listFutures.add(es.submit(new CallableImageLoader(1, "http://chudesa-mira.ru/uploads/images/00/00/21/2011/11/17/872999.jpg")));
        listFutures.add(es.submit(new CallableImageLoader(2, "http://unnatural.ru/wp-content/uploads/2011/04/Bugatti-Veyron-16.4-Super-Sport-1.jpg")));
        listFutures.add(es.submit(new CallableImageLoader(3, "http://bom.flenda.ru/images/gallery/97/327_1000x750.jpg")));
        listFutures.add(es.submit(new CallableImageLoader(4, "http://bom.flenda.ru/images/gallery/97/322_1000x750.jpg")));
        listFutures.add(es.submit(new CallableImageLoader(5, "http://img11.nnm.me/9/5/c/f/0/95cf0985d6a5b0233e01669da5fe77c9_full.jpg")));
        listFutures.add(es.submit(new CallableImageLoader(6, "http://img.gazeta.ru/files3/717/3990717/bmw-pic452-452x452-36449.jpg")));
        listFutures.add(es.submit(new CallableImageLoader(7, "http://telegraf.com.ua/files/2012/10/bmv_-x6_-sinyaya_avtomobili_mashiny_avto.jpg")));
        listFutures.add(es.submit(new CallableImageLoader(8, "http://pro-rap.net/uploads/posts/2013-09/1380105844_331651.jpg")));
        listFutures.add(es.submit(new CallableImageLoader(9, "http://original-news.ru/wp-content/uploads/2013/02/Bugatti-Veyron-Super-Sport-%D0%B4%D0%BE%D1%80%D0%BE%D0%B3%D0%BE%D0%B9-%D0%B0%D0%B2%D1%82%D0%BE%D0%BC%D0%BE%D0%B1%D0%B8%D0%BB%D1%8C.jpg")));
        listFutures.add(es.submit(new CallableImageLoader(10, "http://neobychno.com/img/2011/11/1-bugatti-veyron1-revise1.jpg")));
        for (Future future: listFutures) {
            try {
                System.out.println(future.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
        es.shutdown();
    }
}

Думаю достаточно, список "можно" и так длинный...

Вариант 4 Используем фреймворк ForkJoin


Ну тут сразу, код:

/**
 * Created by Andrienko Alexander on 09.07.2014.
 */
public class ForkJoinTaskImageLoader extends RecursiveAction{
    private String url;
    private int i;

    public ForkJoinTaskImageLoader(int i, String url) {
        this.url = url;
        this.i = i;
    }

    @Override
    protected void compute() {
        Loader loader = new Loader();
        try {
            loader.setUrlLine(url);
            loader.load();
        } catch (Exception e) {
            System.out.println(e);
        }
        System.out.println(i);
    }
}

public class ApplicationForkJoin {
    public static void main(String[] args) {
        ForkJoinPool forkJoinPool = new ForkJoinPool(4);
        forkJoinPool.execute(new ForkJoinTaskImageLoader(1, "http://chudesa-mira.ru/uploads/images/00/00/21/2011/11/17/872999.jpg"));
        forkJoinPool.execute(new ForkJoinTaskImageLoader(2, "http://unnatural.ru/wp-content/uploads/2011/04/Bugatti-Veyron-16.4-Super-Sport-1.jpg"));
        forkJoinPool.execute(new ForkJoinTaskImageLoader(3, "http://bom.flenda.ru/images/gallery/97/327_1000x750.jpg"));
        forkJoinPool.execute(new ForkJoinTaskImageLoader(4, "http://bom.flenda.ru/images/gallery/97/322_1000x750.jpg"));
        forkJoinPool.execute(new ForkJoinTaskImageLoader(5, "http://img11.nnm.me/9/5/c/f/0/95cf0985d6a5b0233e01669da5fe77c9_full.jpg"));
        forkJoinPool.execute(new ForkJoinTaskImageLoader(6, "http://img.gazeta.ru/files3/717/3990717/bmw-pic452-452x452-36449.jpg"));
        forkJoinPool.execute(new ForkJoinTaskImageLoader(7, "http://telegraf.com.ua/files/2012/10/bmv_-x6_-sinyaya_avtomobili_mashiny_avto.jpg"));
        forkJoinPool.execute(new ForkJoinTaskImageLoader(8, "http://pro-rap.net/uploads/posts/2013-09/1380105844_331651.jpg"));
        forkJoinPool.execute(new ForkJoinTaskImageLoader(9, "http://original-news.ru/wp-content/uploads/2013/02/Bugatti-Veyron-Super-Sport-%D0%B4%D0%BE%D1%80%D0%BE%D0%B3%D0%BE%D0%B9-%D0%B0%D0%B2%D1%82%D0%BE%D0%BC%D0%BE%D0%B1%D0%B8%D0%BB%D1%8C.jpg"));
        forkJoinPool.execute(new ForkJoinTaskImageLoader(10, "http://neobychno.com/img/2011/11/1-bugatti-veyron1-revise1.jpg"));
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

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