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的動作

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

2. XlsDataQueue.java
  1. public class XlsDataQueue {
  2. private final LinkedList<XlsData> queue = new LinkedList<XlsData>();
  3. private boolean readEnd;
  4. private long startTime;
  5. public XlsDataQueue(long time){
  6. startTime = time;
  7. }
  8. public synchronized XlsData getXlsData(){
  9. while(queue.size() <= 0){
  10. try {
  11. if(!isReadEnd()){
  12. System.out.println(Thread.currentThread().getName()+": wait() begins");
  13. wait();
  14. System.out.println(Thread.currentThread().getName()+": wait() ends");
  15. }else
  16. break;
  17. } catch (InterruptedException e) {
  18. // TODO Auto-generated catch block
  19. e.printStackTrace();
  20. }
  21. }
  22. return (queue.size() > 0) ? queue.removeFirst() : null;
  23. }
  24. public synchronized void setXlsData(XlsData data){
  25. queue.addLast(data);
  26. notifyAll();
  27. }
  28. public boolean isReadEnd() {
  29. return readEnd;
  30. }
  31. public void setReadEnd(boolean readEnd) {
  32. this.readEnd = readEnd;
  33. }
  34. public long getStartTime() {
  35. return startTime;
  36. }
  37. }

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

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

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

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

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

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

thread的wait。

3. ReadXlsThread
  1. public class ReadXlsThread extends Thread{
  2. private XlsDataQueue queue;
  3. private String execName;
  4. public ReadXlsThread(XlsDataQueue queue, String execName){
  5. this.queue = queue;
  6. this.execName = execName;
  7. }
  8. public void run(){
  9. Workbook xlsBook = null;
  10. try {
  11. xlsBook = Workbook.getWorkbook(new File("example.xls"));
  12. Sheet sheet = xlsBook.getSheet(0);
  13. for(int i = 0 ; i < sheet.getRows() ; i++){
  14. int id = Integer.parseInt(sheet.getCell(0, i).getContents());
  15. String name = sheet.getCell(1, i).getContents();
  16. XlsData data = new XlsData(id, name);
  17. queue.setXlsData(data);
  18. }
  19. queue.setReadEnd(true);
  20. } catch (BiffException e) {
  21. // TODO Auto-generated catch block
  22. e.printStackTrace();
  23. } catch (IOException e) {
  24. // TODO Auto-generated catch block
  25. e.printStackTrace();
  26. } finally{
  27. xlsBook.close();
  28. }
  29. }
  30. }

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

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

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

4. WriteXlsThread
  1. private XlsDataQueue queue;
  2. public WriteXlsThread(XlsDataQueue queue, String execName){
  3. this.queue = queue;
  4. this.execName = execName;
  5. }
  6. public void run(){
  7. try {
  8. Class.forName(driver);
  9. m_conn = DriverManager.getConnection(url, user, password);
  10. ...
  11. float time = (System.currentTimeMillis()-queue.getStartTime());
  12. System.out.println("連結資料庫:"+time+"毫秒");
  13. while(true){
  14. XlsData data = queue.getXlsData();
  15. if(data == null)break;
  16. execDelete.setInt(1, data.getId());
  17. execDelete.executeUpdate();
  18. execInsert.setInt(1, data.getId());
  19. execInsert.setString(2, data.getName());
  20. execInsert.executeUpdate();
  21. }
  22. time = (System.currentTimeMillis()-queue.getStartTime());
  23. System.out.println("執行時間:"+time+"毫秒");
  24. } catch (Exception e) {
  25. // TODO Auto-generated catch block
  26. e.printStackTrace();
  27. } finally{
  28. try {
  29. m_conn.close();
  30. } catch (SQLException e) {
  31. // TODO Auto-generated catch block
  32. e.printStackTrace();
  33. }
  34. }
  35. }

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

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

最後執行結果如下:

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


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

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

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

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

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

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

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

留言