1. 程式人生 > >java中Writer的執行緒安全性

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條中的執行緒安全,寫入速度下降超過一半。

相關推薦

Spring 執行安全性

Spring與執行緒安全   Spring作為一個IOC/DI容器,幫助我們管理了許許多多的“bean”。但其實,Spring並沒有保證這些物件的執行緒安全,需要由開發者自己編寫解決執行緒安全問題的程式碼。   Spring對每個bean提供了一個s

Java停止執行

一.停止執行緒會帶來什麼? 對於單執行緒中,停止單執行緒就是直接使用關鍵字return或者break,但是在停止多執行緒時是讓執行緒在完成任務前去開啟另外一條執行緒,必須放棄當前任務,而這個過程是不可預測,所以必須去做好防備。 二.認識停止執行緒的幾個方法  2.1三個被棄用的

java執行一定快嗎?看完就知道!!!

理解上下文切換   即使是單核處理器也支援多執行緒執行程式碼,CPU通過每個執行緒分配CPU時間片來實現這個機制.時間片是CPU分配給多個執行緒的時間,因為時間片非常短,所以CPU通過不停的切換執行緒執行,讓我們感覺多個執行緒是同時執行的,時間片一般是幾十毫秒(ms).  

深入理解Java停止執行

一.停止執行緒會帶來什麼? 對於單執行緒中,停止單執行緒就是直接使用關鍵字return或者break,但是在停止多執行緒時是讓執行緒在完成任務前去開啟另外一條執行緒,必須放棄當前任務,而這個過程是不可預測,所以必須去做好防備。 二.認識停止執行緒的幾個方法  2.1三個被棄用的方法 &n

Java執行池及其實現類ThreadPoolExecutor

前言:像我們連線資料庫一樣,需要不斷地建立連線,銷燬連線,如果都是人為地一個個建立和銷燬的話會很費勁,所以就誕生了資料庫連線池,執行緒池的產生也是同樣的道理。 執行緒池預先建立了若干數量的執行緒,並且不能由使用者直接對執行緒的建立進行控制,在這個前提下重複使用固定或較為固定數目的執行緒來完成任務

Spring執行安全性

一:Spring與執行緒安全 Spring作為一個IOC/DI容器,幫助我們管理了許許多多的“bean”。但其實,Spring並沒有保證這些物件的執行緒安全,需要由開發者自己編寫解決執行緒安全問題的程式碼。 Spring對每個bean提供了一個scope屬性來表示該bean的作用域。它是be

【小家javaJava執行(父執行)與子執行的通訊和聯絡

相關閱讀 【小家java】java5新特性(簡述十大新特性) 重要一躍 【小家java】java6新特性(簡述十大新特性) 雞肋升級 【小家java】java7新特性(簡述八大新特性) 不溫不火 【小家java】java8新特性(簡述十大新特性) 飽受讚譽 【小家java】java9

【小家javaJava執行池,你真的用對了嗎?(教你用正確的姿勢使用執行池)

相關閱讀 【小家java】java5新特性(簡述十大新特性) 重要一躍 【小家java】java6新特性(簡述十大新特性) 雞肋升級 【小家java】java7新特性(簡述八大新特性) 不溫不火 【小家java】java8新特性(簡述十大新特性) 飽受讚譽 【小家java】java9

如何在JAVA建立執行

ExecutorService 今天小編要分享的是關於執行緒池, 想必接觸到併發處理的朋友都有用到執行緒池, 當我們訪問伺服器的量達到伺服器一定量的時候, 比如幾百萬幾千萬,很容易造成伺服器崩掉, 如果使用執行緒進行併發處理,將使用的執行緒進行回收在使用,就減小了伺服器的壓力

Java併發】Java執行

Java中的執行緒池 執行流程 執行緒池的建立 提交任務 關閉執行緒池 參考 執行流程 處理流程如下: execute()方法執行示意圖如下: 執行緒池的建立 corePoolSize:執行緒池

java 執行

1.概念: 什麼是程序?   概念:在計算機中執行的軟體,是作業系統中最基礎的組成部分 。程序是容器,裡面裝的都是執行緒。 什麼是執行緒?   概念:就是執行在程序中的一段程式碼,是程序中最小組織單元。 注意:  1.一個程序中至少要有一個執行緒,該執行緒必須是主執行緒  2.一個程序中可以有多個執行緒

Java執行執行間的通訊

                                         兩個

JAVA執行池的理解

為了更好的理解,首先來看一篇JAVA中的執行緒池介紹: 一、簡介 執行緒池類為 java.util.concurrent.ThreadPoolExecutor,常用構造方法為: ThreadPoolExecutor(int corePoolSize, int maxim

Java併發基礎—執行安全性

執行緒安全性         當多個執行緒訪問某個類時,不管執行時環境採用何種排程方式或者這些程序如何交替執行,並且在主調程式碼中無需任何額外的同步或協同,這個類都能表現出正確的行為,那麼就稱這個類是執行緒安全的。 執行緒安全性——原子性         提供了互斥訪問

Java執行Thread方法之---suspend()和resume()

案例一:(通過打斷點除錯) package com.threadstop.demo; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.u

Java執行併發體系知識點彙總

一、多執行緒 1、作業系統有兩個容易混淆的概念,程序和執行緒。 程序:一個計算機程式的執行例項,包含了需要執行的指令;有自己的獨立地址空間,包含程式內容和資料;不同程序的地址空間是互相隔離的;程序擁有各種資源和狀態資訊,包括開啟的檔案、子程序和訊號處理。 執行緒:表示程

java建立執行的三種方法以及區別

Java使用Thread類代表執行緒,所有的執行緒物件都必須是Thread類或其子類的例項。Java可以用三種方式來建立執行緒,如下所示: 1)繼承Thread類建立執行緒 2)實現Runnable介面建立執行緒 3)使用Callable和Future建立執行緒 下面

java建立執行

Java使用Thread類代表執行緒,所有的執行緒物件都必須是Thread類或其子類的例項。Java可以用三種方式來建立執行緒,如下所示: 1、繼承Thread類建立執行緒 定義Thread類的子類,並重寫該類的run()方法,建立Thread子類的例項,呼叫執行緒的start()方法啟

Java執行通訊、執行組、未處理的執行異常

執行緒通訊 傳統的執行緒通訊 假設現在系統中有兩個執行緒,這兩個執行緒分別代表存款者和取錢者,而系統有一種特殊的要求,系統要求存款者和取錢者不斷地重複存款、取錢的動作,而且要求每當存款者將錢存入指定賬戶後,取錢者就立即取錢。不允許存款者和取錢者操作連續超過兩次。 為了實現這種功能

如何在JAVA建立:執行

ExecutorService 今天小編要分享的是關於執行緒池, 想必接觸到併發處理的朋友都有用到執行緒池, 當我們訪問伺服器的量達到伺服器一定量的時候, 比如幾百萬幾千萬,很容易造成伺服器崩掉, 如果使用執行緒進行併發處理,將使用的執行緒進行回收在使用,就減小了伺