java高并发多线程及多进程同时写入文件研究

文章目录

  • 测试&思考:
    • java多线程同时写一个文件
        • 第一种情况是:一个线程A有对文件加锁,另一个线程B没对文件加锁
          • 在windows7环境下:(持有锁的可以写文件成功)。
          • 在linux centos 6.3环境下:(都可以写文件成功,表现为数据交叉写入)
        • 第二种情况两个线程都有加锁
          • 在windows7环境和linux centos 6.3环境下表现一样:(持有锁的可以写文件成功)
    • 多进程同时写一个文件
        • 如果同为java进程,一个进程A有对文件加锁,另一个进程B没对文件加锁
          • 在windows7环境下:(持有锁的可以写文件成功)。
          • 在linux centos 6.3环境下:(都可以写文件成功,表现为数据交叉写入)
        • 如果同为java进程,两个进程都加锁
          • 在windows7环境和linux centos 6.3环境下表现一样:
        • 一个为java进程,另一个为非Java进程
          • java进程无锁的情况
          • java进程无锁的情况
  • 总结
  • 测试代码

测试&思考:

环境:windows 7、linux centos 6.3、java8

java多线程同时写一个文件

  java高并发环境下多线程同时写入一个文件时,
通过 FileLock 加锁,可以控制对文件的并发操作。同一个JVM,可以共享部分内存

第一种情况是:一个线程A有对文件加锁,另一个线程B没对文件加锁

在windows7环境下:(持有锁的可以写文件成功)。

  持有锁的线程A会有对文件的操作权限,没加锁的线程B没有对文件的操作权限,会报错退出,如下:

java.io.IOException: 另一个程序已锁定文件的一部分,进程无法访问。
在linux centos 6.3环境下:(都可以写文件成功,表现为数据交叉写入)

  互不影响,线程A和B都有对文件的操作权限

第二种情况两个线程都有加锁

在windows7环境和linux centos 6.3环境下表现一样:(持有锁的可以写文件成功)

  一个线程A竞争到锁,会有对文件的操作权限,另一个线程B没有竞争到锁,没有对文件的操作权限,会报错退出,而不是发生阻塞。如下:

java.nio.channels.OverlappingFileLockException

  在高并的这种生产情况下,需要捕获这个异常,并处理,如下:

while (true) {
    try {
        flout = fcout.tryLock();
        break;
    } catch (Exception e) {
        //计数等其他操作...
        sleep(1000);
    }
}

多进程同时写一个文件

  如果同为java进程,则是不同的JVM。不可以共享内存

如果同为java进程,一个进程A有对文件加锁,另一个进程B没对文件加锁

在windows7环境下:(持有锁的可以写文件成功)。

  持有锁的进程 A会有对文件的操作权限,没加锁的进程 B没有对文件的操作权限,会报错退出,如下:

java.io.IOException: 另一个程序已锁定文件的一部分,进程无法访问。
在linux centos 6.3环境下:(都可以写文件成功,表现为数据交叉写入)

  互不影响,进程A和B都有对文件的操作权限

如果同为java进程,两个进程都加锁

在windows7环境和linux centos 6.3环境下表现一样:

   谁先获得锁,谁先获得对文件的操作权限,另一个进程则会等待第一个进程处理完成,才会获得锁,再对文件进行处理。在这里是发生阻塞,而不是抛出异常(注意与多线程加锁的区别)。

  由此可以证明:针对对多进程同时操作同一个文件,在这里应该是底层JVM层面或者本地方法接口库对这个文件进行了加锁。

一个为java进程,另一个为非Java进程

   此处操作全在服务器centos6.3上测试,非Java进程为简单的 shell 进程,例如:

for((i=1;i<10;i++));do echo 333 >> tmp.txt;sleep 1; done
java进程无锁的情况

   互不影响,java进程和非java进程都有对文件的操作权限

java进程无锁的情况

   互不影响,java进程和非java进程都有对文件的操作权限

总结

   由此可见,在java高并发(无论是多线程还是多进程)同时操作文件时。

  • 如果没有文件顺序的限制,可以不加锁,在这里有操作系统为我们兜底(这里有风险,是不是所有的操作系统都为我们兜底呢)不会产生脏数据;
  • 如果有文件顺序要求的限制,在这里无论是多线程还是多进程(前提是Java服务),都可以得到很好的并发控制
  • 如果可以接受加锁的开销和复杂度,只要遇到并发操作文件时都可以加锁。这样可以保证100%不会乱序,不用考虑是否操作系统会不会为我们兜底了。
  • 如果是使用FileLock try() 方法,同进程内的多线程访问, lock会直接报OverlappingFileLockException, 而不是一直阻塞。 如果是多进程, lock会一直阻塞,而不会包OverlappingFileLockException
  • 这表明java提供的FileLock是面向整个虚拟机的,即进程级别的。合理利用FileLock,加上多线程的异常处理控制。就可以在多线程和多进程场景下实现对文件的高并发访问控制
  • FileLock 作用于java的进程级别,无论独占锁、共享锁都是针对不同的进程,线程之间不适用。

测试代码

package com.dxm.etl.test;

import java.io.*;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;

public class TestFileLock {
    public static void main(String args[]) throws Exception {
        System.out.println(Thread.currentThread().getName());
//        new ThreadWriteFileWithoutLock("111").start();
//        Thread.sleep(1000);
        new ThreadWriteFileWithLock("222").start();
    }

    private static class ThreadWriteFileWithLock extends Thread {
        private String threadName;

        public ThreadWriteFileWithLock(String threadName) {
            this.threadName = threadName;
        }

        public void run() {
            long t1  = System.currentTimeMillis();
            File file = new File("tmp.txt");
            FileOutputStream output = null;
            BufferedWriter br = null;
            FileChannel fileChannel = null;
            try {
                output =  new FileOutputStream(file, true);
                br = new BufferedWriter(new OutputStreamWriter(output,"UTF-8"));
                //对该文件加锁
                fileChannel = output.getChannel();
                FileLock fileLock = null;

                fileLock = fileChannel.lock(0,Long.MAX_VALUE,false);
                System.out.println(fileLock);
                System.out.println(fileLock.isShared());
                //非阻塞
                /*while (true) {
                    try {
                        flout = fcout.tryLock();
                        break;
                    } catch (Exception e) {
                        System.out.println("有其他线程正在操作该文件,当前线程休眠1000毫秒");
                        sleep(1000);
                    }

                }*/

                for (int i = 1; i <= 10; i++) {
                    sleep(1000);
                    br.write(threadName+"\n");
                    br.flush();
                }

                fileLock.release();
            } catch (Exception e) {
                e.printStackTrace();
                System.out.println(threadName +" err");
            } finally {
                try {
                    br.close();
                    fileChannel.close();
                    output.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }

            System.out.println(threadName + "有锁,写文件共花了" + (System.currentTimeMillis() - t1) + "ms");
        }
    }
    public static class ThreadWriteFileWithoutLock extends Thread {
        private String threadName;

        public ThreadWriteFileWithoutLock(String threadName) {
            this.threadName = threadName;
        }

        public void run() {
            long t1  = System.currentTimeMillis();
            File file = new File("tmp.txt");
            FileOutputStream output = null;
            BufferedWriter br = null;
            try {
                output =  new FileOutputStream(file, true);
                br = new BufferedWriter(new OutputStreamWriter(output,"UTF-8"));

                for (int i = 1; i <= 10; i++) {
                    sleep(1000);
                    br.write(threadName+"\n");
                    br.flush();
                }

            } catch (Exception e) {
                e.printStackTrace();
                System.out.println(threadName +" err");
            } finally {
                try {
                    br.close();
                    output.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }

            System.out.println(threadName + "无锁,写文件共花了" + (System.currentTimeMillis() - t1) + "ms");
        }
    }
}

参考:
JAVA 文件锁 FileLock
Java处理多人同时读写文件的文件锁处理
Java 进程间文件锁FileLock详解
Linux系统环境下关于多进程并发写同一个文件的讨论

你可能感兴趣的:(java,高并发)