2016-04-21 108 views
1

我很难理解如何通过两个线程同步ArrayList。基本上,我想要一个线程将对象附加到列表中,另一个线程同时从列表中读取对象。通过两个线程同步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 

任何帮助深表感谢

+5

您可以使用'ArrayBlockingQueue'代替。链接:https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ArrayBlockingQueue.html – user2004685

+2

您必须使用ArrayLists吗?来自并发库的队列怎么样? – KevinO

+0

这不是一个可重现的例子... –

回答

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

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

总之,第二个线程在第一个线程向列表添加任何内容之前完成。

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

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秒。根据您的文件加载的时间长短,您将能够看到迄今加载的内容的总和。

0

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数据。

+0

你的例子有两个问题:1)它不等待另一个线程_finish_填充'goodList':它等待另一个线程_start_填充它。 2)它没有同步访问'goodList.isEmpty()'。当线程A更新某个变量(例如列表的长度)时,除非_both_线程正在使用某种同步,否则不能保证何时(甚至_IF_)线程B将能够看到该更改。 –

+0

1.同意。这个例子只是为提问者提供一些进展,并不代表如何编写适当的多线程代码。 2.在while循环中同步'goodList'会导致另一个问题:死锁,这就是为什么我省略了它并尝试阻止可能抛出的'InterruptedException'的原因。 – Saheed

+0

在睡眠时,您不需要(或想要!)在锁上同步,但如果在从内存中获取值时轮询该长度的线程未在锁上同步,则Java语言规范不要求线程永远看到长度> 0,无论有多少项目其他线程添加到列表中。当然,在大多数JVM中,轮询线程_will_的长度> 0,但问题是,除非规范允许的每个JVM行为都正确,否则不能调用该程序。 –