隨筆 - 22, 文章 - 0, 評論 - 1, 引用 - 0
          數據加載中……

          java中Writer的線程安全性

          以前負責一個項目,我負責從一個超大的文本文件中讀取信息存入數據庫再進一步分析。而文本文件內容是每行一個json串。我在解析的過程中發現,有很小的概率json串的結構會破壞,比如前一個json串只寫了半行,后面就被另一個json串覆蓋掉了。
          與產生日志的部門溝通,他們說是多線程使用log4j寫入,可能偶爾會有串行。
          具體他們是否使用log4j的AsyncAppender我不太了解,暫時也沒去看log4j的源碼,當時只是簡單的忽略異常的行了事兒。
          現在比較閑,想測試一下jdk里面各種輸出方式,例如Writer,在多線程交替寫入文件一行時是否會出現串行的情況,于是便出現了本文。
          測試分兩部分:
          1,多個線程各自開啟一個FileWriter寫入同一個文件。
          2,多個線程共用一個FileWriter寫入同一個文件。
          --------------------------------------------------
          首先來看FileWriter的JDK說明:
          “某些平臺一次只允許一個 FileWriter(或其他文件寫入對象)打開文件進行寫入”——如果是這樣,那么第1個測試便不用做了,可事實上至少在windows下并非如此。
          上代碼(別嫌丑,咱是在IO,不是在測多線程,您說是吧?):
          1,多個線程各自開啟一個FileWriter寫入同一個文件。
           1     //在100毫秒的時間內,10個線程各自開一個FileWriter,
           2     //同時向同一個文件寫入字符串,每個線程每次寫一行。
           3     //測試結果:文件內容出現混亂,串行
           4     private void multiThreadWriteFile() throws IOException{
           5         File file=new File(basePath+jumpPath+fileName);
           6         file.createNewFile();
           7         
           8         //創建10個線程
           9         int totalThreads=10;
          10         WriteFileThread[] threads=new WriteFileThread[totalThreads];
          11         for(int i=0;i<totalThreads;i++){
          12             WriteFileThread thread=new WriteFileThread(file,i);
          13             threads[i]=thread;
          14         }
          15         
          16         //啟動10個線程
          17         for(Thread thread: threads){
          18             thread.start();
          19         }
          20         
          21         //主線程休眠100毫秒
          22         try {
          23             Thread.sleep(100);
          24         } catch (InterruptedException e) {
          25             e.printStackTrace();
          26         }
          27         
          28         //所有線程停止
          29         for(WriteFileThread thread: threads){
          30             thread.setToStop();
          31         }
          32         System.out.println("還楞著干什么,去看一下文件結構正確與否啊!");
          33     }

           1     class WriteFileThread extends Thread{
           2         private boolean toStop=false;
           3         private FileWriter writer;
           4         private int threadNum;
           5         private String lineSeparator;
           6         
           7         WriteFileThread(File file,int threadNum) throws IOException{
           8             lineSeparator=System.getProperty("line.separator");
           9             writer=new FileWriter(file,true);
          10             this.threadNum=threadNum;
          11         }
          12         
          13         @Override
          14         public void run() {
          15             while(!toStop){
          16                 try {
          17                     writer.append("線程"+threadNum+"正在寫入文件," +
          18                             "媽媽說名字要很長才能夠測試出這幾個線程有沒有沖突啊," +
          19                             "不過還是沒有論壇里帖子的名字長,怎么辦呢?" +
          20                             "哎呀,后面是換行符了"+lineSeparator);
          21                     
          22                 } catch (IOException e) {
          23                     e.printStackTrace();
          24                 }
          25             }
          26             System.out.println("---------線程"+threadNum+"停止執行了");
          27         }
          28 
          29         public void setToStop() {
          30             this.toStop = true;
          31         }
          32     }
          測試結果:
          產生5MB左右的文本文件,里面出現大約5%的文本串行現象。
          --------------------------------------------------
          接下來我們看多個線程共用一個FileWriter寫入同一個文件的情況:
          在Writer抽象類里面有一個protected類型的lock屬性,是一個簡單Object對象。
          JDK里對這個lock屬性的描述如下:“用于同步針對此流的操作的對象。為了提高效率,字符流對象可以使用其自身以外的對象來保護關鍵部分。因此,子類應使用此字段中的對象,而不是 this 或者同步的方法。 ”——看來,多線程共用同一個writer的方案有戲。
          繼續看下源代碼,從FileWriter的writer方法開始看起,調用過程如下:
          FileWriter->OutputStreamWriter.write->StreamEncoder.write
          其中StreamEncoder.write的源碼如下:
          (JDK自帶源碼不包括StreamExcoder,可以在這里查看 http://www.docjar.com/html/api/sun/nio/cs/StreamEncoder.java.html)
           1 public void write(char cbuf[], int off, int len) throws IOException {
           2     synchronized (lock) {
           3         ensureOpen();
           4         if ((off < 0) || (off > cbuf.length) || (len < 0) ||
           5                 ((off + len) > cbuf.length) || ((off + len) < 0)) 
           6             {
           7                 throw new IndexOutOfBoundsException();
           8             } else if (len == 0) {
           9                 return;
          10             }
          11         implWrite(cbuf, off, len);
          12     }
          13 }
          可以看到FileWriter在寫入時,同步在了對應的FileOutputStream對象上——依此分析,多個線程共用一個FileWriter寫入同一個文件,一次一行的情況下,不會出現串行。
          寫代碼測試一下:
           1     //多線程爭搶寫入同一個文件的測試,一次一行
           2     //多個線程公用一個FileWriter
           3     //測試結果:
           4     private void multiThreadWriteFile2() throws IOException{
           5         File file=new File(basePath+jumpPath+fileName);
           6         file.createNewFile();
           7         FileWriter fw=new FileWriter(file);
           8         
           9         //創建10個線程
          10         int totalThreads=10;
          11         WriteFileThread2[] threads=new WriteFileThread2[totalThreads];
          12         for(int i=0;i<totalThreads;i++){
          13             WriteFileThread2 thread=new WriteFileThread2(fw,i);
          14             threads[i]=thread;
          15         }
          16         
          17         //啟動10個線程
          18         for(Thread thread: threads){
          19             thread.start();
          20         }
          21         
          22         //主線程休眠100毫秒
          23         try {
          24             Thread.sleep(100);
          25         } catch (InterruptedException e) {
          26             e.printStackTrace();
          27         }
          28         
          29         //所有線程停止
          30         for(WriteFileThread2 thread: threads){
          31             thread.setToStop();
          32         }
          33         System.out.println("還楞著干什么,去看一下文件結構正確與否啊!");
          34     }

           1     class WriteFileThread2 extends Thread{
           2         private boolean toStop=false;
           3         private FileWriter writer;
           4         private int threadNum;
           5         private String lineSeparator;
           6         
           7         WriteFileThread2(FileWriter writer,int threadNum){
           8             lineSeparator=System.getProperty("line.separator");
           9             this.writer=writer;
          10             this.threadNum=threadNum;
          11         }
          12         
          13         @Override
          14         public void run() {
          15             while(!toStop){
          16                 try {
          17                     writer.append("線程"+threadNum+"正在寫入文件," +
          18                             "媽媽說名字要很長才能夠測試出這幾個線程有沒有沖突啊," +
          19                             "不過還是沒有論壇里帖子的名字長,怎么辦呢?" +
          20                             "哎呀,后面是換行符了"+lineSeparator);
          21                 } catch (IOException e) {
          22                     e.printStackTrace();
          23                 }
          24             }
          25             System.out.println("---------線程"+threadNum+"停止執行了");
          26         }
          27 
          28         public void setToStop() {
          29             this.toStop = true;
          30         }
          31     }
          測試結果:
          產生2.2MB左右的文本文件,里面沒有出現任何串行現象。
          --------------------------------------------------
          那么BufferedWriter又如何呢?
          按道理BufferedWriter只是把別的Writer裝飾了一下,在底層寫的時候也是同步的。
          看源碼:
          1     void flushBuffer() throws IOException {
          2         synchronized (lock) {
          3             ensureOpen();
          4             if (nextChar == 0)
          5                 return;
          6             out.write(cb, 0, nextChar);
          7             nextChar = 0;
          8         }
          9     }
          BufferedWriter.write和BufferedWriter.flushBuffer的方法同步在了被包裝的Writer這個對象上。
          也就是說,BufferedWriter.write和BufferedWriter.flushBuffer都有同步塊包圍,說明按上述環境測試時,是不會出現串行現象的。
          --------------------------------------------------
          最終結果:
          1,windows下,可以開多個線程操作多個FileWriter寫入同一個文件,多個FileWriter切換時,會導致相互交錯,破壞字符串結構的完整性。
          2,多個線程操作FileWriter或者BufferedWriter時,每一次寫入操作都是可以保證原子性的,也即:FileWriter或者BufferedWriter是線程安全的——呃,這個結論貌似好簡單啊,JDK文檔里有說明嗎?沒看到啊。
          3,由于第2條中的線程安全,寫入速度下降超過一半。

          posted on 2012-09-02 00:13 王星游 閱讀(3380) 評論(0)  編輯  收藏 所屬分類: java

          主站蜘蛛池模板: 泾川县| 佛学| 肇源县| 武陟县| 赣州市| 广平县| 林州市| 曲松县| 赤城县| 永修县| 洪江市| 莱芜市| 四会市| 广元市| 五指山市| 昌都县| 永兴县| 当阳市| 龙游县| 阿拉善左旗| 古蔺县| 东港市| 姚安县| 青阳县| 桃园市| 文山县| 九台市| 白沙| 光山县| 新源县| 黑河市| 崇左市| 伊金霍洛旗| 六枝特区| 垣曲县| 龙南县| 桑植县| 阳山县| 盐源县| 百色市| 合江县|