Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1619944
  • 博文数量: 695
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 4027
  • 用 户 组: 普通用户
  • 注册时间: 2013-11-20 21:22
文章分类

全部博文(695)

文章存档

2018年(18)

2017年(74)

2016年(170)

2015年(102)

2014年(276)

2013年(55)

分类: Java

2017-06-06 09:10:24

以前负责一个项目,我负责从一个超大的文本文件中读取信息存入数据库再进一步分析。而文本文件内容是每行一个json串。我在解析的过程中发现,有很小的概率json串的结构会破坏,比如前一个json串只写了半行,后面就被另一个json串覆盖掉了。
与产生日志的部门沟通,他们说是多线程使用log4j写入,可能偶尔会有串行。
具体他们是否使用log4j的AsyncAppender我不太了解,暂时也没去看log4j的源码,当时只是简单的忽略异常的行了事儿。
现在比较闲,想测试一下jdk里面各种输出方式,例如Writer,在多线程交替写入文件一行时是否会出现串行的情况,于是便出现了本文。
测试分两部分:
1,多个线程各自开启一个FileWriter写入同一个文件。
2,多个线程共用一个FileWriter写入同一个文件。
--------------------------------------------------
首先来看FileWriter的JDK说明:
“某些平台一次只允许一个 FileWriter(或其他文件写入对象)打开文件进行写入”——如果是这样,那么第1个测试便不用做了,可事实上至少在windows下并非如此。
上代码(别嫌丑,咱是在IO,不是在测多线程,您说是吧?):
1,多个线程各自开启一个FileWriter写入同一个文件。


点击(此处)折叠或打开

  1. 1 //在100毫秒的时间内,10个线程各自开一个FileWriter,
  2.  2 //同时向同一个文件写入字符串,每个线程每次写一行。
  3.  3 //测试结果:文件内容出现混乱,串行
  4.  4 private void multiThreadWriteFile() throws IOException{
  5.  5 File file=new File(basePath+jumpPath+fileName);
  6.  6 file.createNewFile();
  7.  7
  8.  8 //创建10个线程
  9.  9 int totalThreads=10;
  10. 10 WriteFileThread[] threads=new WriteFileThread[totalThreads];
  11. 11 for(int i=0;i<totalThreads;i++){
  12. 12 WriteFileThread thread=new WriteFileThread(file,i);
  13. 13 threads[i]=thread;
  14. 14 }
  15. 15
  16. 16 //启动10个线程
  17. 17 for(Thread thread: threads){
  18. 18 thread.start();
  19. 19 }
  20. 20
  21. 21 //主线程休眠100毫秒
  22. 22 try {
  23. 23 Thread.sleep(100);
  24. 24 } catch (InterruptedException e) {
  25. 25 e.printStackTrace();
  26. 26 }
  27. 27
  28. 28 //所有线程停止
  29. 29 for(WriteFileThread thread: threads){
  30. 30 thread.setToStop();
  31. 31 }
  32. 32 System.out.println("还楞着干什么,去看一下文件结构正确与否啊!");
  33. 33 }

  34.  1 class WriteFileThread extends Thread{
  35.  2 private boolean toStop=false;
  36.  3 private FileWriter writer;
  37.  4 private int threadNum;
  38.  5 private String lineSeparator;
  39.  6
  40.  7 WriteFileThread(File file,int threadNum) throws IOException{
  41.  8 lineSeparator=System.getProperty("line.separator");
  42.  9 writer=new FileWriter(file,true);
  43. 10 this.threadNum=threadNum;
  44. 11 }
  45. 12
  46. 13 @Override
  47. 14 public void run() {
  48. 15 while(!toStop){
  49. 16 try {
  50. 17 writer.append("线程"+threadNum+"正在写入文件," +
  51. 18 "妈妈说名字要很长才能够测试出这几个线程有没有冲突啊," +
  52. 19 "不过还是没有论坛里帖子的名字长,怎么办呢?" +
  53. 20 "哎呀,后面是换行符了"+lineSeparator);
  54. 21
  55. 22 } catch (IOException e) {
  56. 23 e.printStackTrace();
  57. 24 }
  58. 25 }
  59. 26 System.out.println("---------线程"+threadNum+"停止执行了");
  60. 27 }
  61. 28
  62. 29 public void setToStop() {
  63. 30 this.toStop = true;
  64. 31 }
  65. 32 }
测试结果:
产生5MB左右的文本文件,里面出现大约5%的文本串行现象
----------------------------------***********************-------------------------------------------------------------



接下来我们看多个线程共用一个FileWriter写入同一个文件的情况:
在Writer抽象类里面有一个protected类型的lock属性,是一个简单Object对象。
JDK里对这个lock属性的描述如下:“用于同步针对此流的操作的对象。为了提高效率,字符流对象可以使用其自身以外的对象来保护关键部分。因此,子类应使用此字段中的对象,而不是 this 或者同步的方法。 ”——看来,多线程共用同一个writer的方案有戏。
继续看下源代码,从FileWriter的writer方法开始看起,调用过程如下:
FileWriter->OutputStreamWriter.write->StreamEncoder.write
其中StreamEncoder.write的源码如下:
(JDK自带源码不包括StreamExcoder,可以在这里查看 )

点击(此处)折叠或打开

  1. public void write(char cbuf[], int off, int len) throws IOException {
  2.  2 synchronized (lock) {
  3.  3 ensureOpen();
  4.  4 if ((off < 0) || (off > cbuf.length) || (len < 0) ||
  5.  5 ((off + len) > cbuf.length) || ((off + len) < 0))
  6.  6 {
  7.  7 throw new IndexOutOfBoundsException();
  8.  8 } else if (len == 0) {
  9.  9 return;
  10. 10 }
  11. 11 implWrite(cbuf, off, len);
  12. 12 }
  13. 13 }
可以看到FileWriter在写入时,同步在了对应的FileOutputStream对象上——依此分析,多个线程共用一个FileWriter写入同一个文件,一次一行的情况下,不会出现串行。
写代码测试一下:

点击(此处)折叠或打开

  1. 1 //多线程争抢写入同一个文件的测试,一次一行
  2.  2 //多个线程公用一个FileWriter
  3.  3 //测试结果:
  4.  4 private void multiThreadWriteFile2() throws IOException{
  5.  5 File file=new File(basePath+jumpPath+fileName);
  6.  6 file.createNewFile();
  7.  7 FileWriter fw=new FileWriter(file);
  8.  8
  9.  9 //创建10个线程
  10. 10 int totalThreads=10;
  11. 11 WriteFileThread2[] threads=new WriteFileThread2[totalThreads];
  12. 12 for(int i=0;i<totalThreads;i++){
  13. 13 WriteFileThread2 thread=new WriteFileThread2(fw,i);
  14. 14 threads[i]=thread;
  15. 15 }
  16. 16
  17. 17 //启动10个线程
  18. 18 for(Thread thread: threads){
  19. 19 thread.start();
  20. 20 }
  21. 21
  22. 22 //主线程休眠100毫秒
  23. 23 try {
  24. 24 Thread.sleep(100);
  25. 25 } catch (InterruptedException e) {
  26. 26 e.printStackTrace();
  27. 27 }
  28. 28
  29. 29 //所有线程停止
  30. 30 for(WriteFileThread2 thread: threads){
  31. 31 thread.setToStop();
  32. 32 }
  33. 33 System.out.println("还楞着干什么,去看一下文件结构正确与否啊!");
  34. 34 }

  35.  1 class WriteFileThread2 extends Thread{
  36.  2 private boolean toStop=false;
  37.  3 private FileWriter writer;
  38.  4 private int threadNum;
  39.  5 private String lineSeparator;
  40.  6
  41.  7 WriteFileThread2(FileWriter writer,int threadNum){
  42.  8 lineSeparator=System.getProperty("line.separator");
  43.  9 this.writer=writer;
  44. 10 this.threadNum=threadNum;
  45. 11 }
  46. 12
  47. 13 @Override
  48. 14 public void run() {
  49. 15 while(!toStop){
  50. 16 try {
  51. 17 writer.append("线程"+threadNum+"正在写入文件," +
  52. 18 "妈妈说名字要很长才能够测试出这几个线程有没有冲突啊," +
  53. 19 "不过还是没有论坛里帖子的名字长,怎么办呢?" +
  54. 20 "哎呀,后面是换行符了"+lineSeparator);
  55. 21 } catch (IOException e) {
  56. 22 e.printStackTrace();
  57. 23 }
  58. 24 }
  59. 25 System.out.println("---------线程"+threadNum+"停止执行了");
  60. 26 }
  61. 27
  62. 28 public void setToStop() {
  63. 29 this.toStop = true;
  64. 30 }
  65. 31 }
测试结果:
产生2.2MB左右的文本文件,里面没有出现任何串行现象。
------------------------------------------**************************-----------------------------------------------


那么BufferedWriter又如何呢?
按道理BufferedWriter只是把别的Writer装饰了一下,在底层写的时候也是同步的。
看源码:

点击(此处)折叠或打开

  1. 1 void flushBuffer() throws IOException {
  2. 2 synchronized (lock) {
  3. 3 ensureOpen();
  4. 4 if (nextChar == 0)
  5. 5 return;
  6. 6 out.write(cb, 0, nextChar);
  7. 7 nextChar = 0;
  8. 8 }
  9. 9 }

BufferedWriter.write和BufferedWriter.flushBuffer的方法同步在了被包装的Writer这个对象上。
也就是说,BufferedWriter.write和BufferedWriter.flushBuffer都有同步块包围,说明按上述环境测试时,是不会出现串行现象的。
-------------------------------------------********************--------------------------------

最终结果:
1,windows下,可以开多个线程操作多个FileWriter写入同一个文件,多个FileWriter切换时,会导致相互交错,破坏字符串结构的完整性。
2,多个线程操作FileWriter或者BufferedWriter时,每一次写入操作都是可以保证原子性的,也即:FileWriter或者BufferedWriter是线程安全的——呃,这个结论貌似好简单啊,JDK文档里有说明吗?没看到啊。
3,由于第2条中的线程安全,写入速度下降超过一半。



阅读(1493) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~