【问题标题】:Multiple threads working off the same list of strings, in java?在java中,多个线程处理相同的字符串列表?
【发布时间】:2015-04-16 13:37:48
【问题描述】:

我正在尝试找出让多个线程从同一个字符串列表中工作的最佳方法。例如,假设我有一个单词列表,并且我希望多个线程打印出该列表中的每个单词。

这是我想出的。该线程使用了一个while循环,当迭代器有next时,它会打印出来并将其从列表中删除。

import java.util.*;
public class ThreadsExample {

    static Iterator it;

    public static void main(String[] args) throws Exception {

        ArrayList<String> list = new ArrayList<>();

        list.add("comet");
        list.add("planet");
        list.add("moon");
        list.add("star");
        list.add("asteroid");
        list.add("rocket");
        list.add("spaceship");
        list.add("solar");
        list.add("quasar");
        list.add("blackhole");


        it = list.iterator();

        //launch three threads
        RunIt rit = new RunIt();

        rit.runit();
        rit.runit();
        rit.runit();

    }
}

class RunIt implements Runnable {

    public void run()
    {
        while (ThreadsExample.it.hasNext()) {
            //Print out and remove string from the list
            System.out.println(ThreadsExample.it.next());

            ThreadsExample.it.remove();
        }
    }

    public void runit() {
        Thread thread = new Thread(new RunIt());
        thread.start();
    }
}

这似乎可行,虽然我在运行过程中遇到了一些Exception in thread "Thread-2" Exception in thread "Thread-0" java.lang.IllegalStateException 错误:

线程“Thread-1”中的异常线程“Thread-0”中的异常
java.lang.IllegalStateException 在
java.util.ArrayList$Itr.remove(ArrayList.java:864) 在
RunIt.run(ThreadsExample.java:44) 在
java.lang.Thread.run(Thread.java:745) java.lang.IllegalStateException
在 java.util.ArrayList$Itr.remove(ArrayList.java:864) 在
RunIt.run(ThreadsExample.java:44) 在
java.lang.Thread.run(Thread.java:745)

我这样做是正确的还是有更好的方法让多个线程在同一个字符串池上工作?

【问题讨论】:

  • 制作一个启动线程的可运行文件是不必要的复杂。
  • 请注意,使用 Iterator.remove() 是一个很好的开始(它会阻止通常的problem of removing items in a loop)。不过,这不是并发访问安全的。
  • 在不同的线程中使用相同的迭代器看起来,嗯,很奇怪。常用的方法是使用队列。

标签: java string multithreading


【解决方案1】:

更好的方法是使用并发队列。 Queue 接口旨在将元素保存在结构中在处理它们之前

    final Queue<String> queue = new ConcurrentLinkedQueue<String>();
    queue.offer("asteroid");

    ExecutorService executorService = Executors.newFixedThreadPool(4);

    executorService.execute(new Runnable() {
        public void run() {
            System.out.println(queue.poll());
        }
    });

    executorService.shutdown();

【讨论】:

    【解决方案2】:

    尝试使用List.synchronizedList将列表创建为同步列表

    像这样更新你的代码:

    ArrayList<String> list = Collections.synchronizedList(new ArrayList<>());
    

    【讨论】:

    • 你确定同步的列表迭代器也同步了吗?
    • 您可以扩展 ArrayList 并覆盖所有函数,为每个 synchronized{} 添加一个包装 return super.something();,我认为这不太实用。
    • @Sasha True 我不确定是不是这样。
    • Collections.synchronizedList() 产生一个 List ,其中各个方法是同步的,但这还不足以解决原始代码中的问题。每个 (hasNext(), next(), remove()) 三元组都必须作为一个原子组执行,否则可能会出现不需要的行为(例如 IllegalStateExceptionNoSuchElementException)。
    【解决方案3】:

    我这样做是正确的还是有更好的方法让多个线程在同一个字符串池上工作?

    你做得不对。您的代码未正确同步,因此其行为未明确定义。有很多方法可以解决您提出的一般问题,但可以修复特定代码中的问题的一种方法是更改​​ RunIt.run() 以正确同步:

        public void run()
        {
            while (true) {
                synchronized(ThreadsExample.it) {
                    if (ThreadsExample.it.hasNext()) {
                        //Print out and remove string from the list
                        System.out.println(ThreadsExample.it.next());
    
                        ThreadsExample.it.remove();
                    } else {
                        break;
                    }
                }
            }
        }
    

    请注意,hasNext() 检查、检索下一个元素以及删除该元素都在同一个同步块中处理,以确保这些操作的相互一致性。另一方面,该块的范围包含在循环内,因此并发执行循环的不同线程每个都有机会执行。

    还要注意,虽然在这种情况下所有线程都在Iterator 对象上同步,但这基本上只是为了方便(对我而言)。只要它们都在同一个对象上同步,那么哪个对象并不重要。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-11-29
      • 1970-01-01
      • 2021-03-01
      • 2012-08-20
      • 1970-01-01
      • 1970-01-01
      • 2023-02-26
      • 1970-01-01
      相关资源
      最近更新 更多