在項目中,遇到一個需求是讀取日誌文件內容,解析後將內容寫入到html文件中。
日誌文件介紹,每一行表示一條交易信息。讀取一行的一條信息將其解析,即使對數據進行處理,之後寫入到html文件中。
讀文件採用的是正則表達式,每匹配到一條信息就解析。
在寫入html文件時,會出現一個線程正在進行寫操作,而另一個線程也要訪問文件。爲了避免寫內容時出現混亂情況,這樣的情況是不允許發生的。這時就需要對文件進行加鎖處理。即使一個線程在對文件進行操作時,其他線程是不能對文件進行操作的。
解決的思路是,每當有線程訪問文件時就對文件進行加鎖處理,寫操作完畢之後釋放鎖。其他線程只有獲得鎖才能對文件進行操作。否則就一直等待,直到獲得文件鎖。
之前用的是 ReentrantLock 這個類對文件進行加鎖。下面是代碼:
private final ReentrantLock lock = new ReentrantLock(); public void doVMenuAccessOutPutHtml(File file, ServiceData sd) { RandomAccessFile out = null; lock.lock(); try { if (!file.exists()) { file.createNewFile(); out = new RandomAccessFile(file, "rw"); writehead(out); writeVMenuHead(out); writefoot(out); } out = new RandomAccessFile(file, "rw"); String style = ""; if (count.get() % 2 == 0) { style = " <tr>\r\n<td> "; } else { style = " <tr class=\"alt\">\r\n<td> "; } StringBuffer sb = new StringBuffer(); String dateTime = sd.getString("dateTime"); String trandate = sd.getString("trandate"); String trancode = sd.getString("trancode"); String orgcode = sd.getString("orgcode"); String clerk = sd.getString("clerk"); String terminal = sd.getString("terminal"); String errcode = sd.getString("errcode"); String errstr = sd.getString("errstr"); sb.append(style + count.incrementAndGet() + " </td>\r\n<td> " + dateTime + " </td>\r\n<td> " + trandate + " </td>\r\n<td> " + trancode + " </td>\r\n<td> " + orgcode + " </td>\r\n<td> " + clerk + " </td>\r\n<td> " + terminal + " </td>\r\n<td> " + errcode + " </td>\r\n<td> " + errstr + " </td>\r\n</tr>\r\n "); long fileLength = out.length(); out.seek(fileLength - 26); out.write(sb.toString().getBytes("utf-8")); writefoot(out); } catch (IOException e) { // file.deleteOnExit(); System.out.println("Exception encountered: " + e); } finally { lock.unlock(); try { out.close(); out = null; } catch (IOException e) { System.out.println("Exception encountered: " + e); } } }
可是當文件兩太大時,會產生很多線程對文件進行操作,就出現了寫入文件混亂的情況。
如下圖:
寫入混亂導致頁面出現混亂的情況。
問題原因我的理解是所有的線程有用的同一個鎖導致。(這裏具體的原因我還是不太明白,希望哪位博友能指點一下。感激不盡)
在網上找了一些資料後,將ReentrantLock換成了FileLock 。實現代碼如下:
public static void doVMenuAccessOutPutHtml(File file, ServiceData sd) { RandomAccessFile out = null; try { if (!file.exists()) { file.createNewFile(); out = new RandomAccessFile(file, "rw"); writehead(out); writeVMenuHead(out); writefoot(out); } out = new RandomAccessFile(file, "rw"); FileChannel fcout = out.getChannel(); FileLock flout = null; while (true) { try { flout = fcout.lock(); break; } catch (Exception e) { System.out.println("有其他線程正在操作該文件,當前線程休眠1000毫秒"); } } String style = ""; if (countVMenu.get() % 2 == 0) style = " <tr>\r\n<td> "; else style = " <tr class=\"alt\">\r\n<td> "; StringBuffer sb = new StringBuffer(); String dateTime = sd.getString("dateTime"); String trandate = sd.getString("trandate"); String trancode = sd.getString("trancode"); String orgcode = sd.getString("orgcode"); String clerk = sd.getString("clerk"); String terminal = sd.getString("terminal"); String errcode = sd.getString("errcode"); String errstr = sd.getString("errstr"); sb.append(style + countVMenu.incrementAndGet() + " </td>\r\n<td> " + dateTime + " </td>\r\n<td> " + trandate + " </td>\r\n<td> " + trancode + " </td>\r\n<td> " + orgcode + " </td>\r\n<td> " + clerk + " </td>\r\n<td> " + terminal + " </td>\r\n<td> " + errcode + " </td>\r\n<td> " + errstr + " </td>\r\n</tr>\r\n "); long fileLength = out.length(); out.seek(fileLength - 26); out.write(sb.toString().getBytes("gbk")); writefoot(out); flout.release(); fcout.close(); out.close(); out = null; } catch (IOException e) { file.deleteOnExit(); System.out.println("Exception encountered: " + e); } }
程序爲每一個線程都定義了鎖。問題得到了解決。
爲什麼用第二種方法就可以了呢?
這裏我也說不出具體的原因,如果你知道的話,希望能指點一下喲。