2016-04-21 54 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行爲都正確,否則不能調用該程序。 –