【问题标题】:Synchronise ArrayList over two threads在两个线程上同步 ArrayList
【发布时间】:2016-08-15 03:38:30
【问题描述】:

我很难理解如何在两个线程上同步 ArrayList。基本上,我希望一个线程将对象附加到列表中,而另一个线程同时从该列表中读取。

这是部署线程的类:

public class Main {
    public static ArrayList<Good> goodList = new ArrayList();
    public static void main(String[] args) {
        Thread thread1 = new Thread(new GoodCreator());
        Thread thread2 = new Thread(new WeightCounter());
        thread1.start();
        thread2.start();
    }
}

然后是两个Runnable类:

这个从文本文件中读取两个值的行并附加新对象。

public class GoodCreator implements Runnable{
    private ArrayList<Good> goodList = Main.goodList;
    private static Scanner scan;
    @Override
    public void run() {
        System.out.println("Thread 1 started");
        int objCount = 0;
        try {
            scan = new Scanner(new File(System.getProperty("user.home") + "//Goods.txt"));
        } catch (FileNotFoundException e) {
            System.out.println("File not found!");
            e.printStackTrace();
        }
        while(scan.hasNextLine()){
            String line = scan.nextLine();
            String[] words = line.split("\\s+");
            synchronized(goodList){
                goodList.add(new Good(Integer.parseInt(words[0]), Integer.parseInt(words[1])));
                objCount++;
            }
            if(objCount % 200 == 0) System.out.println("created " + objCount + " objects");
        }
    }

}

这会遍历 arraylist 并且应该总结其中一个字段。

public class WeightCounter implements Runnable{
    private ArrayList<Good> goodList = Main.goodList;
    @Override
    public void run() {
        System.out.println("Thread 2 started");
        int weightSum = 0;
        synchronized(goodList){
            for(Good g : goodList){
                weightSum += g.getWeight();
            }
        }
        System.out.println(weightSum);

    }

}

无论输入如何,weightSum 永远不会增加并保持为 0

Thread 1 started
Thread 2 started
0

非常感谢任何帮助

【问题讨论】:

  • 您可以改用ArrayBlockingQueue。链接:docs.oracle.com/javase/7/docs/api/java/util/concurrent/…
  • 必须使用 ArrayLists 吗?来自并发库的队列怎么样?
  • 这不是一个可重复的例子......
  • @YassinHajaj 这就是并发错误很难找到的原因:它们通常是不可重现的。在这种情况下,两个线程之间存在明显的数据竞争。

标签: java multithreading arraylist


【解决方案1】:

这就是所谓的生产者-消费者任务。你可以用 arraylist 来做,但老实说,这不是解决这个问题的正确方法。

幸运的是,Java 为我们提供了一些集合,即 BlockingQueue 集合,它们就是为此专门设计的;

//the collection with the stuff in it
static BlockingQueue<Object> items = new BlockingQueue<Object>();
//(there are a few different types of blocking queues, check javadocs.
//you would want Linked or Array blocking queue

//what happens on the reader thread
public void producer()
{
    //read the data into the collection
    for (all the data in the file)
    {
       //add the next item
       items.put(/* next item from file or w/e */);

       //stop if necessary
       if (atEndOfFile) stillReadingData = false;

       //etc
    }
}

现在您需要从队列中读取数据 - 幸运的是这很容易;

//what happens on the other threads
public void consumer()
{


   //keep this thread alive so long as there is data to process
   //or so long as there might be more data to process
   while (stillReadingData || !items.isEmpty())
   {
       //get the next item from the list
       //while the list is empty, we basically sleep for "timeout" timeunits,
       //then the while-loop would repeat, and so on
       Object o = items.poll(long timeout, int units);
       if (o != null) //process it
   }
}

通过这种方式,您可以使用生产者线程不断地将项目添加到队列中,并且一旦消费者线程空闲,这些项目就会被处理(这种方法适用于大量消费者线程)。如果您仍然需要为这些项目收集一个集合,那么您应该创建第二个集合,并在它们被处理后将它们添加到该集合中。

附带说明,您可能仍需要同步处理项目时发生的操作。例如,您需要在“weightSum”上同步增量(或者使用 AtomicInteger)。

【讨论】:

    【解决方案2】:

    WeightCounter 类中尝试此更改。

    public class WeightCounter implements Runnable{
       private ArrayList<Good> goodList = Main.goodList;
       @Override
       public void run() {
          System.out.println("Thread 2 started");
          int weightSum = 0;
          while(goodList.isEmpty()) {
            Thread.sleep(1000);
          }
          synchronized(goodList){
            for(Good g : goodList){
                weightSum += g.getWeight();
            }
         }
         System.out.println(weightSum);
       }
    }
    

    此更改将导致WeightCounter 线程在尝试读取之前等待其他线程完成向goodList 填充数据。

    【讨论】:

    • 你的例子有两个问题:1)它不等待另一个线程finish填充goodList:它等待另一个线程start 填充它。 2)它访问goodList.isEmpty()而不同步。当线程 A 更新某个变量(例如,列表的长度)时,无法保证线程 B 何时(甚至 IF)能够看到更改,除非 both 线程正在使用某种同步。
    • 1.同意。该示例只是为提问者提供一些进展,并不代表应该如何编写适当的多线程代码。 2.在while循环中同步goodList会导致另一个问题:死锁这就是为什么我省略它并尝试阻止可能抛出的InterruptedException
    • 您不需要(或不希望!)在睡眠时同步锁,但是如果轮询长度的线程在从内存中获取值时没有同步锁,那么Java 语言规范不要求线程看到长度 > 0,无论其他线程添加多少项到列表中。当然,在大多数 JVM 中,轮询线程看到长度 > 0,但关键是,除非它对于规范允许的每个 JVM 行为都是正确的,否则不能正确调用程序.
    【解决方案3】:

    您正在运行两个独立运行的线程。这些线程可以按任何顺序运行,如果一个停止,例如要从文件中读取,另一个线程不会假定它必须等待它。

    简而言之,您的第二个线程在第一个线程向列表中添加任何内容之前完成。

    没有很好的解决方法,因为这不是一个很好的例子来说明为什么要使用多个线程,但是要获得结果,你可以这样做。

    public class WeightCounter implements Runnable{
        private ArrayList<Good> goodList = Main.goodList;
        @Override
        public void run() {
            System.out.println("Thread 2 started");
            for(int i = 0; i < 10; i++) {
               try {
                   Thread.sleep(100);
               } catch (InterruptedException ie) {
                   throw AssertionError(ie);
               }
               int weightSum = 0;
               synchronized(goodList){
                   for (Good g : goodList)
                        weightSum += g.getWeight();
               }
               System.out.println(weightSum);
            }
        }
    }
    

    这将打印 10 次总和,相隔 0.1 秒。根据您的文件加载所需的时间,您将能够看到到目前为止已加载内容的总和。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-11-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-10-20
      • 2011-05-05
      相关资源
      最近更新 更多