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 王星游 阅读(465) 评论(0)  编辑  收藏 所属分类:java

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值