繁体   English   中英

如何使用 ExecutorService 在多个线程中将元素正确添加到 ConcurrentHashMap

[英]How to properly add elements to ConcurrentHashMap in multiple threads using ExecutorService

我正在尝试使用多个线程将图像从某个文件夹加载到 ConcurrentHashMap 以节省时间。 不幸的是,一些线程在尝试加载并将图像放入我的 map 时“卡住”。 结果,当调用 shutdown() 程序时,即使某些线程没有执行它们的任务,程序也会走得更远。 当我将 ExecutorService 线程 pule 设置为 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;
}
};

该问题很可能与ConcurrentHashMap无关。 每次,您在 map 中put一些东西,其他线程将无法同时put 所以也许,一些线程将不得不等到另一个线程完成put ,但这不会导致任何竞争条件。

我在我的机器上执行了你的代码,一切正常。 (没有错误消息,打印加载图像的数量)。 也许您的计算机在加载图像方面没有我的那么快,因此awaitTermination超时。

据我所知,我不知道您的方法(使用多线程加载图像)是否是个好主意。 您的硬盘驱动器(或 SSD)将成为瓶颈,您的线程最终将等待硬盘驱动器(语句ImageIO.read )。 此外,启动执行器服务(分别启动新线程)并不是很便宜,所以也许你最好不使用多线程。 特别是因为您只需要加载一次图像(之后,它们被缓存在地图中),因此加速可能永远不会显着。 我会考虑按顺序加载图像。

ImageIO 非常慢并且非常 I/O 密集,因此添加许多线程通常对典型的 PC 没有帮助。 您确定您不需要为 awaitTermination 超时添加大量数字吗?

另一种选择是对线程池使用长度有限的 LinkBlockingQueue,这样当消费者速度较慢时,您的主应用程序线程就会变慢。 这意味着结束时延迟 5L 秒是现实的,以允许正在进行的呼叫结束。

请参阅 newFixedThreadPool(n) 的 JDK 源代码,在 LinkedBlockingQueue() 的构造函数中尝试 qSize = say 2 or 3 x nthreads

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM