`
王星游
  • 浏览: 5006 次
文章分类
社区版块
存档分类
最新评论

java中Writer的线程安全性

    博客分类:
  • java
阅读更多

 

以前负责一个项目,我负责从一个超大的文本文件中读取信息存入数据库再进一步分析。而文本文件内容是每行一个json串。我在解析的过程中发现,有很小的概率json串的结构会破坏,比如前一个json串只写了半行,后面就被另一个json串覆盖掉了。

与产生日志的部门沟通,他们说是多线程使用log4j写入,可能偶尔会有串行。

具体他们是否使用log4j的AsyncAppender我不太了解,暂时也没去看log4j的源码,当时只是简单的忽略异常的行了事儿。

现在比较闲,想测试一下jdk里面各种输出方式,例如Writer,在多线程交替写入文件一行时是否会出现串行的情况,于是便出现了本文。

 

测试分两部分:

1,多个线程各自开启一个FileWriter写入同一个文件。

2,多个线程共用一个FileWriter写入同一个文件。

--------------------------------------------------

首先来看FileWriter的JDK说明:

“某些平台一次只允许一个 FileWriter(或其他文件写入对象)打开文件进行写入”——如果是这样,那么第1个测试便不用做了,可事实上至少在windows下并非如此。

 

上代码(别嫌丑,咱是在IO,不是在测多线程,您说是吧?):

1,多个线程各自开启一个FileWriter写入同一个文件。

 

	//在100毫秒的时间内,10个线程各自开一个FileWriter,
	//同时向同一个文件写入字符串,每个线程每次写一行。
	//测试结果:文件内容出现混乱,串行
	private void multiThreadWriteFile() throws IOException{
		File file=new File(basePath+jumpPath+fileName);
		file.createNewFile();
		
		//创建10个线程
		int totalThreads=10;
		WriteFileThread[] threads=new WriteFileThread[totalThreads];
		for(int i=0;i<totalThreads;i++){
			WriteFileThread thread=new WriteFileThread(file,i);
			threads[i]=thread;
		}
		
		//启动10个线程
		for(Thread thread: threads){
			thread.start();
		}
		
		//主线程休眠100毫秒
		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		//所有线程停止
		for(WriteFileThread thread: threads){
			thread.setToStop();
		}
		System.out.println("还楞着干什么,去看一下文件结构正确与否啊!");
	}
 

 

	class WriteFileThread extends Thread{
		private boolean toStop=false;
		private FileWriter writer;
		private int threadNum;
		private String lineSeparator;
		
		WriteFileThread(File file,int threadNum) throws IOException{
			lineSeparator=System.getProperty("line.separator");
			writer=new FileWriter(file,true);
			this.threadNum=threadNum;
		}
		
		@Override
		public void run() {
			while(!toStop){
				try {
					writer.append("线程"+threadNum+"正在写入文件," +
							"妈妈说名字要很长才能够测试出这几个线程有没有冲突啊," +
							"不过还是没有论坛里帖子的名字长,怎么办呢?" +
							"哎呀,后面是换行符了"+lineSeparator);
					
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			System.out.println("---------线程"+threadNum+"停止执行了");
		}

		public void setToStop() {
			this.toStop = true;
		}
	}
 

 

测试结果:

产生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)

 

public void write(char cbuf[], int off, int len) throws IOException {
	synchronized (lock) {
		ensureOpen();
		if ((off < 0) || (off > cbuf.length) || (len < 0) ||
				((off + len) > cbuf.length) || ((off + len) < 0)) 
			{
				throw new IndexOutOfBoundsException();
			} else if (len == 0) {
				return;
			}
		implWrite(cbuf, off, len);
	}
}
 

可以看到FileWriter在写入时,同步在了对应的FileOutputStream对象上——依此分析,多个线程共用一个FileWriter写入同一个文件,一次一行的情况下,不会出现串行。

写代码测试一下:

 

	//多线程争抢写入同一个文件的测试,一次一行
	//多个线程公用一个FileWriter
	//测试结果:
	private void multiThreadWriteFile2() throws IOException{
		File file=new File(basePath+jumpPath+fileName);
		file.createNewFile();
		FileWriter fw=new FileWriter(file);
		
		//创建10个线程
		int totalThreads=10;
		WriteFileThread2[] threads=new WriteFileThread2[totalThreads];
		for(int i=0;i<totalThreads;i++){
			WriteFileThread2 thread=new WriteFileThread2(fw,i);
			threads[i]=thread;
		}
		
		//启动10个线程
		for(Thread thread: threads){
			thread.start();
		}
		
		//主线程休眠100毫秒
		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		//所有线程停止
		for(WriteFileThread2 thread: threads){
			thread.setToStop();
		}
		System.out.println("还楞着干什么,去看一下文件结构正确与否啊!");
	}
 

 

	class WriteFileThread2 extends Thread{
		private boolean toStop=false;
		private FileWriter writer;
		private int threadNum;
		private String lineSeparator;
		
		WriteFileThread2(FileWriter writer,int threadNum){
			lineSeparator=System.getProperty("line.separator");
			this.writer=writer;
			this.threadNum=threadNum;
		}
		
		@Override
		public void run() {
			while(!toStop){
				try {
					writer.append("线程"+threadNum+"正在写入文件," +
							"妈妈说名字要很长才能够测试出这几个线程有没有冲突啊," +
							"不过还是没有论坛里帖子的名字长,怎么办呢?" +
							"哎呀,后面是换行符了"+lineSeparator);
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			System.out.println("---------线程"+threadNum+"停止执行了");
		}

		public void setToStop() {
			this.toStop = true;
		}
	}
 

 

测试结果:

产生2.2MB左右的文本文件,里面没有出现任何串行现象。

--------------------------------------------------

那么BufferedWriter又如何呢?

按道理BufferedWriter只是把别的Writer装饰了一下,在底层写的时候也是同步的。

看源码:

 

    void flushBuffer() throws IOException {
        synchronized (lock) {
            ensureOpen();
            if (nextChar == 0)
                return;
            out.write(cb, 0, nextChar);
            nextChar = 0;
        }
    }
 

BufferedWriter.write和BufferedWriter.flushBuffer的方法同步在了被包装的Writer这个对象上。

也就是说,BufferedWriter.write和BufferedWriter.flushBuffer都有同步块包围,说明按上述环境测试时,是不会出现串行现象的。

--------------------------------------------------

最终结果:

1,windows下,可以开多个线程操作多个FileWriter写入同一个文件,多个FileWriter切换时,会导致相互交错,破坏字符串结构的完整性。

2,多个线程操作FileWriter或者BufferedWriter时,每一次写入操作都是可以保证原子性的,也即:FileWriter或者BufferedWriter是线程安全的——呃,这个结论貌似好简单啊,JDK文档里有说明吗?没看到啊。

3,由于第2条中的线程安全,写入速度下降超过一半。


分享到:
评论

相关推荐

    java程序 两个线程实现学生成绩的读写

    这样的设计可以提高程序的效率,同时需要确保数据的一致性和安全性。 首先,我们需要理解Java中的线程。线程是程序中的执行单元,每个线程都有自己的程序计数器、虚拟机栈、本地方法栈和一部分堆内存。在Java中创建...

    java多线程聊天室

    - 为了保证数据的一致性和安全性,多线程环境下可能需要使用同步机制,如synchronized关键字、Lock接口、Semaphore等,来避免线程间的竞态条件。 6. **用户界面**: - Control类可能提供一个简单的命令行界面,让...

    java多线程实现-tcp端口扫描

    为了确保线程安全,我们需要考虑同步问题。在记录开启的端口到结果文件时,可能会有多个线程同时尝试写入数据。为防止数据冲突,我们可以使用Java的synchronized关键字或者Lock接口来实现互斥访问。此外,还可以使用...

    使用javaGUI多线程网络编程技术实现的聊天室程序

    在本项目中,"使用Java GUI多线程网络编程技术实现的聊天室程序"是一个基于Java平台的实时通信应用,其主要目标是模仿QQ的功能,为用户提供即时消息传递的能力。这个程序利用了Java的几个核心特性,包括图形用户界面...

    JAVA_API1.6文档(中文)

    java.util.concurrent.atomic 类的小工具包,支持在单个变量上解除锁的线程安全编程。 java.util.concurrent.locks 为锁和等待条件提供一个框架的接口和类,它不同于内置同步和监视器。 java.util.jar 提供读写 ...

    java基础部分的笔记,包含抽象,特征,映射,流,线程,反射等

    这份"java基础部分的笔记"涵盖了Java编程中的关键概念和技术,包括抽象、特征、映射、流、线程以及反射,这些都是理解Java核心特性和进行高效编程所必须掌握的知识。 1. **抽象**:在Java中,抽象是面向对象编程的...

    java基础代码_java_输入输出流_接口_多线程_异常_

    线程间的通信和同步是多线程编程的重点,Java提供了synchronized关键字、wait()、notify()和notifyAll()方法以及Lock和Condition等工具来确保线程安全。 异常处理是Java中的另一个重要概念。Java使用异常...

    java jdk api中文开发文档(免币)

    9. **泛型**:泛型引入了类型参数,增强了代码的类型安全性和重用性。 10. **枚举类型**:Java 5引入的枚举类型,提供了更安全的替代常量类的方式。 11. **注解(Annotation)**:注解提供了一种元数据,可以用于...

    java基础反射IO流线程模式

    - **懒汉式单例模式**:第一次使用时才初始化,提高了加载速度,但线程安全性需额外考虑。 #### 代理模式 代理模式为其他对象提供一种代理以控制对这个对象的访问。 ### Socket Socket是用于在网络中进行通信的...

    java开发手册 api文档(jdk1.8中文)

    此外,`synchronized`关键字和`volatile`变量确保了线程安全,而`Lock`接口及其实现如`ReentrantLock`则提供了更细粒度的锁控制。 在网络编程方面,`java.net`包提供了网络通信的基本工具,如`Socket`和`...

    Java中文1.6API

    Java中文1.6 API是专为那些在编程过程中遇到英文文档阅读困难的开发者设计的一份资源,它提供了Java 1.6版本的核心类库和接口的详细中文解释,帮助开发者更好地理解和使用Java语言进行程序开发。这个API文档覆盖了...

    java中文API文档

    - **synchronized关键字**:用于同步,确保多线程环境下的数据一致性。 - **wait()、notify()和notifyAll()**:对象级别的线程间通信方法。 5. **网络编程** - **Socket和ServerSocket**:用于TCP/IP通信。 - *...

    JAVA API中文文档

    8. **泛型**:Java 5引入的泛型提高了代码的类型安全性和重用性,允许在定义类、接口和方法时指定参数类型。 9. **枚举**:`enum`关键字用于定义枚举类型,提供了更安全的常量表示方式,比传统的`final static`变量...

    Java 1.6 API 中文 New

    java.util.concurrent.atomic 类的小工具包,支持在单个变量上解除锁的线程安全编程。 java.util.concurrent.locks 为锁和等待条件提供一个框架的接口和类,它不同于内置同步和监视器。 java.util.jar 提供读写 JAR ...

    Java多线程之readwritelock读写分离的实现代码

    Java多线程之readwritelock读写分离的实现代码 Java多线程之readwritelock读写分离的实现...ReadWriteLock 是 Java 中实现读写分离的重要机制,它可以帮助我们在多线程环境下保护共享资源,提高程序的性能和稳定性。

    有关Java的学习总结,到多线程

    Java是一种广泛使用的面向对象的编程语言,以其跨平台、高...多线程编程涉及到进程间通信、死锁预防、线程安全等问题,需要深入理解线程的本质和Java提供的多线程支持机制。不断实践和探索,你将在Java世界中游刃有余。

    java入门基础部分总概思维导图

    * Java的多态性允许对象以不同的方式来表现。 Java异常处理 * Java的异常处理机制包括`try`、`catch`、`finally`三个块。 * Java的异常可以分为checked exception和unchecked exception两种。 * Java的异常处理...

    Java高级教程课件 java数据库教程 JDBC教程 8-IO&amp;线程总结(共7页).pptx

    3. **创建Statement或PreparedStatement**:`Statement`用于执行静态SQL,而`PreparedStatement`支持预编译的SQL,提供更好的性能和安全性。 4. **执行SQL**:通过`executeQuery()`或`executeUpdate()`方法执行SQL。...

    Java API_5.0中文版

    1. **泛型(Generics)**:Java 5.0引入了泛型,允许在类、接口和方法声明中使用类型参数,增强了类型安全性和代码重用性。例如,ArrayList中的T就是泛型的一个应用,表示ArrayList可以存储任意类型的元素。 2. **...

Global site tag (gtag.js) - Google Analytics