Как правильно добавлять элементы в ConcurrentHashMap в нескольких потоках с помощью ExecutorService - PullRequest
1 голос
/ 20 июня 2020

Я пытаюсь загрузить изображения из какой-то папки в ConcurrentHashMap, используя несколько потоков, чтобы сэкономить время. К сожалению, некоторые потоки «застревают», когда они пытаются загрузить и поместить изображение на мою карту. В результате при вызове shutdown () программа идет дальше, даже если некоторые потоки не выполнили свои задачи. Когда я устанавливаю пул потоков ExecutorService на 1, все идет нормально, но я трачу много времени на ожидание загрузки всех изображений. Мне кажется, что есть некоторые проблемы с гонками, но, как я знаю, ConcurrentHashMap безопасен для многопоточных операций. Я все еще новичок, поэтому позвольте мне понять, в чем проблема и что у меня плохо получается. Вот код:

public abstract class ImageContainer {

private final static Map<String, BufferedImage> imageMap = loadImages();
private static long loadingTime;

public static Map<String, BufferedImage> loadImages() {

    loadingTime = System.currentTimeMillis();
    ConcurrentHashMap<String, BufferedImage> imageMap = new ConcurrentHashMap<>();
    ExecutorService es = Executors.newFixedThreadPool(5);
    File imageDirectory = new File("Images/");
    if (!imageDirectory.isDirectory()) {
        System.out.println("Image directory error");
    }
    File[] files = imageDirectory.listFiles();
    if (files != null) {
        for (File file : files) {
            if (file.isFile()) {
                es.submit(new Runnable(){
                    @Override
                    public void run() {
                        try{
                            if(file.getAbsolutePath().contains(".jpg")) {
                                imageMap.put(file.getName().replace(".jpg",""),ImageIO.read(file));
                            }
                            else if (file.getAbsolutePath().contains(".png")) {
                                imageMap.put(file.getName().replace(".png",""),ImageIO.read(file));
                            }
                        }
                        catch (IOException e)
                        {
                            System.out.println("Cannot load image");
                        }
                    }
                });
            }
        }
    }
    else
    {
        System.out.println("Image folder empty!");
    }

    es.shutdown();
    try {
        if(!es.awaitTermination(5L, TimeUnit.SECONDS)) {
            System.out.println("Images did not load successfully!");
            es.shutdownNow();
        }
        loadingTime = System.currentTimeMillis() - loadingTime;
    }
    catch(InterruptedException e) {
        System.out.println("Loading images interrupted!");
    }
    System.out.println(imageMap.size());
    return imageMap;
}
};

Ответы [ 2 ]

0 голосов
/ 20 июня 2020

ImageIO довольно медленный и очень интенсивный по вводу-выводу, поэтому добавление большого количества потоков часто не помогает на обычных ПК. Вы уверены, что вам просто не нужно добавлять большое число для тайм-аута awaitTermination?

Другой вариант - использовать LinkBlockingQueue с ограниченной длиной для пулов потоков, чтобы ваш основной поток приложения замедлялся, когда потребители медленные. Это означает, что временная задержка 5L секунд в конце - это реалистично c, чтобы разрешить завершение текущих вызовов.

См. Источник JDK для newFixedThreadPool (n), попробуйте qSize = скажем, 2 или 3 x nthreads в contructor в LinkedBlockingQueue ()

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}
0 голосов
/ 20 июня 2020

Проблема скорее всего не связана с ConcurrentHashMap. Каждый раз, когда вы put что-то на карте, ни один другой поток не сможет put одновременно. Так что, возможно, некоторым потокам придется подождать, пока другой не закончит работу с put, но это не вызовет каких-либо условий гонки.

Я выполнил ваш код на своей машине, и все работает. (Нет сообщения об ошибке, печатает количество загруженных изображений). Возможно, ваш компьютер не так быстро загружает изображения, как мой, и поэтому время ожидания awaitTermination истекает.

Насколько я могу судить, я не знаю, подходит ли ваш подход (загрузка изображений с многопоточностью) такая хорошая идея. Ваш жесткий диск (или SSD) будет узким местом, и ваши потоки в конечном итоге будут ждать жесткий диск (инструкция ImageIO.read). Кроме того, развертывание службы исполнителя (или запуск новых потоков) не очень дешево, поэтому, возможно, вам лучше отказаться от многопоточности. Тем более, что вам нужно загрузить изображения только один раз (после этого они кэшируются на карте), поэтому ускорение, вероятно, никогда не будет значительным. Я бы подумал о последовательной загрузке изображений.

...