Java 以线程安全的方式获取集合的内容

Java 以线程安全的方式获取集合的内容,java,multithreading,collections,Java,Multithreading,Collections,我想以多线程方式阅读java集合的内容。这里有很多相同背景的问题,但没有一个是关于具体阅读点的 我有一个整数集合。我只需要几个线程在上面迭代,每个线程一次提取一个整数。我希望确保所有集合都是迭代的,并且两个不同的线程不会两次提取整数 坦率地说,我不知道什么是有效的。我知道迭代器不是线程安全的,但当涉及到只读时,我不知道。我做了一些测试,试图找出线程错误,但没有达到100%的确定性: intimax=500; 集合li=新阵列列表(imax); 对于(int i=0;i

我想以多线程方式阅读java集合的内容。这里有很多相同背景的问题,但没有一个是关于具体阅读点的

我有一个整数集合。我只需要几个线程在上面迭代,每个线程一次提取一个整数。我希望确保所有集合都是迭代的,并且两个不同的线程不会两次提取整数

坦率地说,我不知道什么是有效的。我知道迭代器不是线程安全的,但当涉及到只读时,我不知道。我做了一些测试,试图找出线程错误,但没有达到100%的确定性:

intimax=500;
集合li=新阵列列表(imax);
对于(int i=0;i
编辑: 在实际用例中,这个整数中的每一个都用于开始一项密集的工作,例如查找它是否为素数

上面的示例提取整数列表,没有重复或未命中,但我不知道这是否是偶然的

使用HashSet而不是ArrayList也可以,但这可能是偶然的


如果您有一个常规集合(不一定是列表),并且需要以多线程方式提取其内容,那么在实践中该怎么办?

您可以使用
java.util.Collections
中提供的同步版本。或者您可以在
java.util.concurrent
中尝试特殊的数据结构(例如
ConcurrentHashMap

我更喜欢这两种,而不是我自己的

另一个想法是在必要时同步整个方法,而不仅仅是集合访问


记住,不可变对象总是线程安全的。您只需要同步共享的可变状态。

这取决于集合。如果在读取过程中没有发生结构变化,那么您可以同时读取它,这很好。大多数集合不会更改只读或迭代的结构,因此这是可以的,但在执行此操作之前,请确保阅读您正在使用的集合的文档

例如:

请注意,此实现是不同步的。如果有多个线程 并发访问哈希集,并至少访问其中一个线程 修改集合时,它必须在外部同步

这意味着,只要不存在写操作,同时从两个线程读取就可以了


一种方法是分割数据,让每个线程读取
collection.size()/numberOfThreads
元素。
线程#我将从
collection.size()/numThreads*i
读取到
collection.size()/numThreads*(i+1)

(注意:需要特别注意以确保最后一个元素不会丢失,可以通过将最后一个线程frpm
collection.size()/numThreads*i
设置为
collection.size()
,但这可能会使最后一个线程做更多的工作,并使您等待挣扎的线程)


另一个选项是使用间隔的任务队列,每个线程将在队列不为空时读取元素,并在给定的间隔内读取元素。队列必须同步,因为它是由多个线程同时修改的。

通常,通过迭代收集内容的成本不足以实现多线程。这是您在获取内容后对列表所做的操作。 所以你应该做的是:

  • 使用单线程获取内容并分配工作负载
  • 启动多个线程/作业来进行处理,为它们提供(大量)工作负载。确保线程不使用原始列表
  • 使用单个线程组合结果
  • 如果需要共享集合,请使用线程安全集合。它们可以通过使用.synchronized。。。功能。但是请记住,这意味着线程必须相互等待,如果您没有大量的工作,这将使您的程序比单线程版本慢


    请注意,在线程之间共享的所有对象都需要是线程安全的(例如,通过将所有访问包装在同步块中)。关于这一点的最佳信息来源是

    您的用例将受益于使用队列-有一些线程安全的实现,例如ArrayBlockingQueue

    Collection<Integer> li = new ArrayList<Integer>(imax);
    final BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(li.size(), false, li);
    
    Thread[] threads = new Thread[20];
    for (int i = 0; i < threads.length; i++) {
        threads[i] = new Thread("Thread " + i) {
            @Override
            public void run() {
                Integer i;
                while ((i = queue.poll()) != null) {
                    System.out.println(i);
                }
            }
        };
    }
    
    Collection li=newarraylist(imax);
    final BlockingQueue=new ArrayBlockingQueue(li.size(),false,li);
    线程[]线程=新线程[20];
    对于(int i=0;i

    这是线程安全的,每个线程都可以独立于初始集合中的其他线程工作。

    Ok。但请大家想想:如果我同步整个方法,我就失去了多线程的好处。@Jean-Yves:不太可能。我相信
    ConcurrentHashMap
    使用细粒度同步和/或允许同时进行多个读取。但这可能是错误的。但是,这种同步仍然有很多开销,如果您知道具体的实例,就可以避免这些开销