迭代ConcurrentHashMap的值是否线程安全?

31 浏览
0 Comments

迭代ConcurrentHashMap的值是否线程安全?

ConcurrentHashMap的javadoc中,有以下内容:\n

\n检索操作(包括get)通常不会阻塞,因此可能与更新操作(包括put和remove)重叠。检索反映了在其开始时保持的最近完成的更新操作的结果。对于像putAll和clear这样的聚合操作,同时进行的检索可能仅反映了某些条目的插入或删除。类似地,迭代器和枚举器返回在迭代器/枚举器创建时或自其创建以来的某一点的哈希表状态反映的元素。它们不会抛出ConcurrentModificationException异常。然而,迭代器只设计为一次由一个线程使用。\n

\n这是什么意思?如果我尝试同时使用两个线程迭代映射会发生什么?如果我在迭代映射时放入或删除一个值会发生什么?

0
0 Comments

从上述内容中可以整理出(Is iterating ConcurrentHashMap values thread safe?)这个问题的出现的原因以及解决方法。

问题的出现原因是由于在多个线程之间共享了一个迭代器对象,而在ConcurrentHashMap中,迭代器对象是不具备线程安全性的。所以,如果多个线程同时使用同一个迭代器对象进行迭代操作,就会导致线程安全问题。

解决这个问题的方法是,应该为每个线程创建一个独立的迭代器对象,并在各个线程中分别使用这些独立的迭代器对象进行迭代操作。这样可以避免多个线程之间对同一个迭代器对象进行并发操作,从而保证线程安全。

代码示例:

ConcurrentHashMap map = new ConcurrentHashMap<>();
// 添加元素到ConcurrentHashMap中
// 创建一个独立的迭代器对象
Iterator iterator = map.values().iterator();
// 在各个线程中使用独立的迭代器对象进行迭代操作
while (iterator.hasNext()) {
    Integer value = iterator.next();
    // 对value进行处理
}

通过以上的解决方法,我们可以确保在多线程环境下迭代ConcurrentHashMap的values是线程安全的。

0
0 Comments

从上面的内容中可以看出,问题的原因是由于多个线程共享了同一个迭代器(iterator)导致的。在第一个示例中,使用了ConcurrentHashMap的entrySet方法返回的迭代器进行遍历操作,由于ConcurrentHashMap是线程安全的,所以不会抛出异常。但是在第二个示例中,将迭代器作为参数传递给了多个Accessor线程和一个Mutator线程,这样就会导致线程之间相互干扰,进而引发并发问题。具体来说,当Accessor线程和Mutator线程同时对迭代器进行操作时,会发生java.lang.IllegalStateException异常。

解决这个问题的方法是,每个线程都使用自己的迭代器进行遍历操作,而不是共享同一个迭代器。可以在每个线程中创建一个新的迭代器对象,并将其作为参数传递给相应的线程。这样就可以避免多个线程之间的干扰,确保线程安全。

以下是整理后的文章:

使用ConcurrentHashMap的values方法进行迭代操作是否线程安全?

在使用ConcurrentHashMap进行迭代操作时,我们可能会遇到线程安全的问题。下面我们通过一个示例来说明这个问题。

示例代码如下:

import java.util.Map;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ConcurrentMapIteration {
    private final Map map = new ConcurrentHashMap();
    private final static int MAP_SIZE = 100000;
    public static void main(String[] args) {
        new ConcurrentMapIteration().run();
    }
    public ConcurrentMapIteration() {
        for (int i = 0; i < MAP_SIZE; i++) {
            map.put("key" + i, UUID.randomUUID().toString());
        }
    }
    private final ExecutorService executor = Executors.newCachedThreadPool();
    private final class Accessor implements Runnable {
        private final Map map;
        public Accessor(Map map) {
            this.map = map;
        }
        public void run() {
            for (Map.Entry entry : this.map.entrySet()) {
                System.out.println(
                        Thread.currentThread().getName() + " - [" + entry.getKey() + ", " + entry.getValue() + ']'
                );
            }
        }
    }
    private final class Mutator implements Runnable {
        private final Map map;
        private final Random random = new Random();
        public Mutator(Map map) {
            this.map = map;
        }
        public void run() {
            for (int i = 0; i < 100; i++) {
                this.map.remove("key" + random.nextInt(MAP_SIZE));
                this.map.put("key" + random.nextInt(MAP_SIZE), UUID.randomUUID().toString());
                System.out.println(Thread.currentThread().getName() + ": " + i);
            }
        }
    }
    private void run() {
        Accessor a1 = new Accessor(this.map);
        Accessor a2 = new Accessor(this.map);
        Mutator m = new Mutator(this.map);
        executor.execute(a1);
        executor.execute(m);
        executor.execute(a2);
    }
}

在上述示例中,我们使用了ConcurrentHashMap的values方法返回的迭代器进行遍历操作,并没有抛出任何异常。这是因为ConcurrentHashMap是线程安全的,可以在多个线程中同时进行读取操作。

但是,当我们将迭代器作为参数传递给多个Accessor线程和一个Mutator线程时,就会出现线程安全的问题。具体来说,当Accessor线程和Mutator线程同时对迭代器进行操作时,会发生java.lang.IllegalStateException异常。

为了解决这个问题,我们可以为每个线程创建一个新的迭代器对象,并将其作为参数传递给相应的线程。这样就可以避免多个线程之间的干扰,确保线程安全。

以下是修改后的示例代码:

import java.util.Iterator;
import java.util.Map;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ConcurrentMapIteration {
    private final Map map = new ConcurrentHashMap();
    private final Iterator> iterator;
    private final static int MAP_SIZE = 100000;
    public static void main(String[] args) {
        new ConcurrentMapIteration().run();
    }
    public ConcurrentMapIteration() {
        for (int i = 0; i < MAP_SIZE; i++) {
            map.put("key" + i, UUID.randomUUID().toString());
        }
        this.iterator = this.map.entrySet().iterator();
    }
    private final ExecutorService executor = Executors.newCachedThreadPool();
    private final class Accessor implements Runnable {
        private final Iterator> iterator;
        public Accessor(Iterator> iterator) {
            this.iterator = iterator;
        }
        public void run() {
            while (iterator.hasNext()) {
                Map.Entry entry = iterator.next();
                try {
                    String st = Thread.currentThread().getName() + " - [" + entry.getKey() + ", " + entry.getValue() + ']';
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
    private final class Mutator implements Runnable {
        private final Map map;
        private final Random random = new Random();
        private final Iterator> iterator;
        public Mutator(Map map, Iterator> iterator) {
            this.map = map;
            this.iterator = iterator;
        }
        public void run() {
            while (iterator.hasNext()) {
                try {
                    iterator.remove();
                    this.map.put("key" + random.nextInt(MAP_SIZE), UUID.randomUUID().toString());
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    }
    private void run() {
        Accessor a1 = new Accessor(this.iterator);
        Accessor a2 = new Accessor(this.iterator);
        Mutator m = new Mutator(map, this.iterator);
        executor.execute(a1);
        executor.execute(m);
        executor.execute(a2);
    }
}

通过为每个线程创建一个新的迭代器对象,我们可以避免迭代器的共享和线程安全问题。

0
0 Comments

在使用ConcurrentHashMap进行迭代时,可能会出现线程安全的问题。原因是每个从ConcurrentHashMap获得的迭代器都是为单个线程设计的,不应该传递给其他线程使用。这包括for-each循环提供的语法糖。

如果尝试在两个线程同时迭代该映射,只要每个线程使用自己的迭代器,就可以正常工作。

如果在迭代映射时放入或移除值,保证不会出现问题(这是ConcurrentHashMap中“concurrent”一词的一部分含义)。然而,并不能保证一个线程将看到另一个线程对映射所做的更改(除非从映射中获取一个新的迭代器)。迭代器保证反映其创建时映射的状态。后续的更改可能会反映在迭代器中,但不一定会如此。

总之,像下面的语句:

for (Object o : someConcurrentHashMap.entrySet()) {
    // ...
}

几乎每次看到它时都是可行的(或者至少是安全的)。但是,如果在迭代过程中,另一个线程从映射中移除了一个对象o10,会发生什么呢?即使已经被移除,我还能在迭代中看到o10吗?

如上所述,没有明确规定现有的迭代器是否会反映对映射的后续更改。因此,我不知道,根据规范,没有人知道(除非查看代码,而且每次运行时更新可能会改变)。因此,您不能依赖它。

但是,如果在迭代ConcurrentHashMap时仍然遇到ConcurrentModificationException异常,那么应该提供触发该异常的代码,并发布一个新问题,但我非常怀疑这是直接从迭代并发容器中引起的问题,除非Java实现有错误。

0