Java - Import excel to Database by Guarded suspension

在這邊將提到的是Guarded suspension設計模式,將excel檔的資料透過一thread負責讀取,再

利用三支thread來進行寫入的動作至資料庫。如此一來,比起單一一支程式先讀取再寫入的

動作將也許將會更有效率來達成目的。

拜讀結城浩先生的Java多執行緒與平行處理,在看到第三章的Guarded Suspension Pattern後,

就自行修改他裡面提到的概念,應用在excel讀檔及寫入資料庫的動作,發現雖然同時一讀一

寫可以提高效率,不過由於在setData及getData的method都有加上synchronized的同步處理(會

降低效率),因此在針對write thread提高為三支時,整個執行時間才有顯著的減少,比起先

讀後寫的方式處理。

PS. 在此不會說明thread的概念,網路上資源很豐富

接下來,就以程式來做簡要的說明Guarded Suspension目的

1. Main.java

在主程式內會使用到三個類別,分別為

XlsDataQueue - 定義容器(Queue)、getXlsData、setXlsData等method
ReadXlsThread - 讀取Excel檔的Thread,用到setXlsData method
WriteXlsThread - 寫入資料到DB的Thread,用到getXlsData method

初始化XlsDataQueue,並且開始一個ReadXlsThread及三個WriteXlsThread的動作

XlsDataQueue xlsQueue = new XlsDataQueue(System.currentTimeMillis());
new ReadXlsThread(xlsQueue, "Reader").start();
new WriteXlsThread(xlsQueue, "##Writer1").start();
new WriteXlsThread(xlsQueue, "##Writer2").start();
new WriteXlsThread(xlsQueue, "##Writer3").start();

2. XlsDataQueue.java
 public class XlsDataQueue {
 private final LinkedList<XlsData> queue = new LinkedList<XlsData>();
 private boolean readEnd;
 private long startTime;
 
 public XlsDataQueue(long time){
  startTime = time;
 }
 
 public synchronized XlsData getXlsData(){
  while(queue.size() <= 0){
   try {
    if(!isReadEnd()){
     System.out.println(Thread.currentThread().getName()+": wait() begins");
     wait();
     System.out.println(Thread.currentThread().getName()+": wait() ends");
    }else
     break;
   } catch (InterruptedException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
   }
  }
  return (queue.size() > 0) ? queue.removeFirst() : null;
 }
 
 public synchronized void setXlsData(XlsData data){
  queue.addLast(data);
  notifyAll();
 }

 public boolean isReadEnd() {
  return readEnd;
 }

 public void setReadEnd(boolean readEnd) {
  this.readEnd = readEnd;
 }

 public long getStartTime() {
  return startTime;
 }
}

XlsDataQueue初始化帶入當下開始的時間點,而getXlsData為WriteXlsThread呼叫使用,會

判斷當下的queue是否還有資料,若沒有資料的話會進行防衛的等待,並且進入wait的動作;

如果不進入迴圈的話,則get第一筆資料。

PS. 當迴圈內的isReadEnd()成立,表示read的動作已經結束了,因此直接break,避免該

WriteThread繼續等待! 因此最後回傳null,在WriteThread程式那將結束動作!(一般為無窮迴圈)

另setXlsData為ReadThread呼叫來set一筆資料至queue內,並且做notifyAll的動作來解除所有

thread的wait。

3. ReadXlsThread
public class ReadXlsThread extends Thread{
   private XlsDataQueue queue;
   private String execName;
   public ReadXlsThread(XlsDataQueue queue, String execName){
     this.queue = queue;
     this.execName = execName;
   }
 
   public void run(){
      Workbook xlsBook = null;
      try {
         xlsBook = Workbook.getWorkbook(new File("example.xls"));
         Sheet sheet = xlsBook.getSheet(0);
         for(int i = 0 ; i < sheet.getRows() ; i++){
           int id = Integer.parseInt(sheet.getCell(0, i).getContents());
           String name = sheet.getCell(1, i).getContents();
           XlsData data = new XlsData(id, name);    
           queue.setXlsData(data);
         }
         queue.setReadEnd(true);
      } catch (BiffException e) {
      // TODO Auto-generated catch block
         e.printStackTrace();
      } catch (IOException e) {
      // TODO Auto-generated catch block
         e.printStackTrace();
      } finally{
         xlsBook.close();
     }
   }
}

在此用jxl套件來寫一簡單的讀取excel檔資料的動作。最後當迴圈執行為後,設定一

readEnd的flag表示讀取動作已結束!

PS. XlsData為一Java Bean,定義了兩個欄位分別為id, name

4. WriteXlsThread
private XlsDataQueue queue;

public WriteXlsThread(XlsDataQueue queue, String execName){
  this.queue = queue;
  this.execName = execName;
}
public void run(){
  try {
     Class.forName(driver);
     m_conn = DriverManager.getConnection(url, user, password);
     ...
   
     float time = (System.currentTimeMillis()-queue.getStartTime());
     System.out.println("連結資料庫:"+time+"毫秒");
   
     while(true){
       XlsData data = queue.getXlsData();
       if(data == null)break;
    
       execDelete.setInt(1, data.getId());
       execDelete.executeUpdate();
    
       execInsert.setInt(1, data.getId());
       execInsert.setString(2, data.getName());
       execInsert.executeUpdate();
     }
   
     time = (System.currentTimeMillis()-queue.getStartTime());
     System.out.println("執行時間:"+time+"毫秒");
  } catch (Exception e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  } finally{
   try {
    m_conn.close();
   } catch (SQLException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
   }
  }
 }

在此主要的code為while迴圈內所執行的動作,當執行queue.getXlsData(),若回傳的為null,

即表示當下readThread已結束動作,此時就跳出無窮迴圈!

最後執行結果如下:

在此讀取的檔案為近5000筆資料,共兩欄


整體看起來,利用three thread做寫入的一方,整個執行時間比讀取完再寫入快一秒鐘左右。

由於當一write thread在執行synchronized method時,其它的write thread無法進入,且要達到

synchronized也應該有一些檢查機制在,多多少少有一些影響吧!

不過,如果資料處理比較複雜的話(excel欄位多,資料量大),寫入時還要做計算處理等!也

許時間處理上會差更多也說不定。

PS. 在此說明的程式為"利用同時讀取寫入執行",另"利用讀取完再寫入執行"的程式主要為

雙方的主要動作合併在一起先後執行,在此就省略。

留言