JAVA多线程读写文件如何做到线程安全?(文件锁,FileChannel)

文章目录

    • NIO提升性能
    • 多线程读写同一个文件有哪些场景需要同步处理?
    • 使用对文件加锁的方式做到线程安全
    • 写文件线程安全
    • 读文件线程安全
    • 小编写的IOListener接口,用于回调
    • 小编写的IOUtils工具类,专门用于文件读写,流的读写
    • 写文件使用示例
    • 读文件使用示例
    • 各位老铁有问题欢迎及时联系、指正、批评、撕逼

NIO提升性能

在JAVA的标准I/O中,提供了基于流的I/O实现,即InputStream和OutputStream。这种基于流的实现以字节为单位处理数据。NIO是New I/O的简称,表示一套新的JAVA I/O标准。在Jdk 1.4中开始引入,它具有以下特性:

  • 为所有的原始类型提供(Buffer)缓存支持;
  • 使用Java.nio.charset.Charset作为字符集编码解码解决方案;
  • 增加通道(Cahnnel)对象,作为新的原始I/O抽象;
  • 支持锁和内存映射文件的文件访问接口;
  • 提供了基于Selector的异步网络I/O。

与流式的I/O不容,NIO是基于块(Block)的,它以块为基本单位处理数据。在NIO中,最为重要的2个组件是缓冲Buffer和通道Channel。缓冲是一块连续的内存块,是NIO读写数据的中转地。通道表示缓冲数据的源头或者目的地,它用于向缓冲读取或者写入数据,是访问缓冲的接口。

JAVA多线程读写文件如何做到线程安全?(文件锁,FileChannel)_第1张图片

多线程读写同一个文件有哪些场景需要同步处理?

  • 有线程正在读文件,另开辟线程写文件;
  • 有线程正在写文件,另开辟线程读文件;
  • 有线程正在写文件,另开辟线程写文件

总之,读写互斥,写读互斥,写写互斥,只有读读相容(可以异步)。

使用对文件加锁的方式做到线程安全

FileInputStream、FileOutputStream、RandomAccessFile均可得到FileChannel对象,对文件锁进行操作。
独占锁tryLock()

FileChannel的tryLock()是非阻塞的,也就是说,在发现文件被锁住的时候,直接返回null,并且抛出异常,如果没有锁住,直接返回该文件的文件锁。
它是独占锁,就是只能被一个线程持有,它能禁止其他线程获取共享锁,可用于写文件。

  while (true) {
                try {
                    fileLock = fileChannel.tryLock();//独占锁
                    break;
                } catch (Exception e) {
                    System.out.println("有其他线程正在操作该文件,当前线程" + Thread.currentThread().getName());
                }
            }

共享锁tryLock(0, Long.MAX_VALUE, true)

FileChannel的tryLock(0, Long.MAX_VALUE, true)是非阻塞的,在发现文件被锁住的时候,直接返回null,并且抛出异常,如果没有锁住,直接返回该文件的文件锁。
它是共享锁,能被多个线程同时持有,它能禁止其他线程获取独占锁,可用于读文件。

while (true) {
                try {
                    fileLock = fileChannel.tryLock(0, Long.MAX_VALUE, true);//共享锁
                    break;
                } catch (Exception e) {
                    System.out.println("有其他线程正在操作该文件,当前线程" + Thread.currentThread().getName());
                }
            }

独占锁lock()

而FileChannel的lock()是阻塞的,在文件被锁定的情况下,会保持阻塞,直到获得该锁为止。

fileLock = fileChannel.lock();
......

写文件线程安全

 /**
     * 将str写入文件,同步操作,独占锁
     */
    public void writeStr2ReplaceFileSync(String str, String pathFile, IOListener ioListener) {
        File file;
        try {
            file = FileUtils.createFile(pathFile);
        } catch (IOException e) {
            e.printStackTrace();
            ioListener.onFail("文件创建失败,请检查路径是否合法以及读写权限");
            return;
        }
        FileOutputStream fileOutputStream = null;
        FileChannel fileChannel = null;
        FileLock fileLock = null;//文件锁
        try {

            /**
             * 写文件
             */
            fileOutputStream = new FileOutputStream(file);
            fileChannel = fileOutputStream.getChannel();
            while (true) {
                try {
                    fileLock = fileChannel.tryLock();//独占锁
                    break;
                } catch (Exception e) {
                    System.out.println("有其他线程正在操作该文件,当前线程" + Thread.currentThread().getName());
                }
            }
            if (fileLock != null) {
                int len = 0;
                long current = file.length();
                if (isRunning ) {
                    fileChannel.write(ByteBuffer.wrap(str.getBytes()));
                    current += len;
                    LogUtils.log("当前线程" + Thread.currentThread().getName());
                    ioListener.onLoading(str.getBytes(), current, str.length());
                }else {
                    //中断
                    ioListener.onInterrupted();
                }
                if (fileLock != null && fileLock.isValid()) {
                    LogUtils.log("release-write-lock");
                    fileLock.release();
                }
                close(fileChannel);
                close(fileOutputStream);
                if (file.length() == str.getBytes().length) {
                    ioListener.onCompleted(file);
                }
            }

        } catch (IOException e) {
            e.printStackTrace();
            ioListener.onFail(e.getMessage());
        } finally {
        }
    }

读文件线程安全

  /**
     * 同步读取,共享锁,但无法同时进行写操作
     *
     * @param ioListener
     */
    public void read2StrSync(String pathFile, IOListener ioListener) {
        FileInputStream fileInputStream = null;
        FileChannel fileChannel = null;
        FileLock fileLock = null;//文件锁

        ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream();
        try {

            /**
             * 读文件
             */

            fileInputStream = new FileInputStream(pathFile);
            fileChannel = fileInputStream.getChannel();
            while (true) {
                try {
                    fileLock = fileChannel.tryLock(0, Long.MAX_VALUE, true);//共享锁
                    break;
                } catch (Exception e) {
                    System.out.println("有其他线程正在操作该文件,当前线程" + Thread.currentThread().getName());
                }
            }
            if (fileLock != null) {

                ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                int len = 0;
                long current = 0;
                while (isRunning && (len = fileChannel.read(byteBuffer)) != -1) {
                    //0,byteBuffer.position(),必须写这个,否则GG,读取文件错乱
                    byteArrayOutputStream.write(byteBuffer.array(),0,byteBuffer.position());
                    current += len;
                    ioListener.onLoading("", current, fileChannel.size());
                    byteBuffer.clear();
                }
                if (fileLock != null && fileLock.isValid()) {
                LogUtils.log("release-read-lock");
                    fileLock.release();
                }
                close(fileChannel);
                close(fileInputStream);
                //中断
                if (len != -1) {
                    ioListener.onInterrupted();
                } else {
                    ioListener.onCompleted(byteArrayOutputStream.toString("utf-8"));

                }
            }


        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            ioListener.onFail(e.getMessage());
        } catch (IOException e) {
            e.printStackTrace();
            ioListener.onFail(e.getMessage());

        } finally {


        }
    }

小编写的IOListener接口,用于回调

public interface IOListener {
    public void onCompleted(T result);
    public void onLoading(T readedPart, long current, long length);
    public void onInterrupted();
    public void onFail(String errorMsg);
}

小编写的IOUtils工具类,专门用于文件读写,流的读写

public class IOUtils {
    private boolean isRunning = true;

    private long contentLength = 0;
    private String encodeType = "utf-8";

    public IOUtils() {
        isRunning = true;
    }

    public IOUtils setContentLength(long contentLength) {
        this.contentLength = contentLength;
        return this;
    }

    public IOUtils setEncodeType(String encodeType) {
        this.encodeType = encodeType;
        return this;

    }

    public static void close(Closeable closeable) {
        if (closeable != null) {
            try {
                closeable.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public void stop() {
        this.isRunning = false;
    }

    public void read(boolean isLine, InputStream inputStream, IOListener ioListener) {
        if (isLine) {
            readLine2String(inputStream, ioListener);
        } else {
            read2String(inputStream, ioListener);

        }
    }

    /**
     * @param ioListener
     */

    public void read2String(String pathFile, IOListener ioListener) {
        FileInputStream fileInputStream = null;
        try {
            fileInputStream = new FileInputStream(pathFile);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            ioListener.onFail(e.getMessage());
            return;
        }
        read2String(fileInputStream, ioListener);
    }

    public void read2String(InputStream inputStream, IOListener ioListener) {

        if (!(inputStream instanceof BufferedInputStream)) {
            inputStream = new BufferedInputStream(inputStream);
        }
        BufferedReader bufferedReader = null;
        InputStreamReader inputStreamReader = null;
        try {
            inputStreamReader = new InputStreamReader(inputStream, encodeType);
            bufferedReader = new BufferedReader(inputStreamReader);

            StringBuilder sb = new StringBuilder();

            char[] buf = new char[1024];
            int len = 0;
            long current = 0;

            while (isRunning && (len = bufferedReader.read(buf)) != -1) {
                sb.append(buf, 0, len);
                current += len;
                ioListener.onLoading("", current, contentLength);
            }

            //中断
            if (len != -1) {
                ioListener.onInterrupted();
            } else {

                ioListener.onCompleted(sb.toString());

            }

        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            ioListener.onFail(e.getMessage());
        } catch (IOException e) {
            e.printStackTrace();
            ioListener.onFail(e.getMessage());

        } finally {
            close(bufferedReader);
            close(inputStreamReader);
            close(inputStream);
        }
    }

    /**
     * 同步读取,共享锁,但无法同时进行写操作
     *
     * @param ioListener
     */
    public void read2StrSync(String pathFile, IOListener ioListener) {
        FileInputStream fileInputStream = null;
        FileChannel fileChannel = null;
        FileLock fileLock = null;//文件锁

        ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream();
        try {

            /**
             * 读文件
             */

            fileInputStream = new FileInputStream(pathFile);
            fileChannel = fileInputStream.getChannel();
            while (true) {
                try {
                    fileLock = fileChannel.tryLock(0, Long.MAX_VALUE, true);//共享锁
                    break;
                } catch (Exception e) {
                    System.out.println("有其他线程正在操作该文件,当前线程" + Thread.currentThread().getName());
                }
            }
            if (fileLock != null) {

                ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                int len = 0;
                long current = 0;
                while (isRunning && (len = fileChannel.read(byteBuffer)) != -1) {
                    //0,byteBuffer.position(),必须写这个,否则GG,读取文件错乱
                    byteArrayOutputStream.write(byteBuffer.array(),0,byteBuffer.position());
                    current += len;
                    ioListener.onLoading("", current, fileChannel.size());
                    byteBuffer.clear();
                }
                if (fileLock != null && fileLock.isValid()) {
                LogUtils.log("release-read-lock");
                    fileLock.release();
                }
                close(fileChannel);
                close(fileInputStream);
                //中断
                if (len != -1) {
                    ioListener.onInterrupted();
                } else {
                    ioListener.onCompleted(byteArrayOutputStream.toString("utf-8"));

                }
            }


        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            ioListener.onFail(e.getMessage());
        } catch (IOException e) {
            e.printStackTrace();
            ioListener.onFail(e.getMessage());

        } finally {


        }
    }

    /**
     * @param ioListener
     */

    public void readLine2String(String pathFile, IOListener ioListener) {
        FileInputStream fileInputStream = null;
        try {
            fileInputStream = new FileInputStream(pathFile);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            ioListener.onFail(e.getMessage());
            return;
        }
        readLine2String(fileInputStream, ioListener);
    }

    /**
     * 一行一行地读
     *
     * @param inputStream
     * @param ioListener
     */
    public void readLine2String(InputStream inputStream, IOListener ioListener) {

        BufferedReader bufferedReader = null;
        InputStreamReader inputStreamReader = null;
        try {
            inputStreamReader = new InputStreamReader(inputStream, encodeType);
            bufferedReader = new BufferedReader(inputStreamReader);

            StringBuilder sb = new StringBuilder();
            long current = 0;

            String str;
            while (isRunning && (str = bufferedReader.readLine()) != null) {
                sb.append(str);
                current += str.length();

                ioListener.onLoading(str, current, contentLength);

            }

            //中断
            if ((str = bufferedReader.readLine()) != null) {
                ioListener.onInterrupted();
            } else {

                ioListener.onCompleted(sb.toString());

            }

        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            ioListener.onFail(e.getMessage());
        } catch (IOException e) {
            e.printStackTrace();
            ioListener.onFail(e.getMessage());

        } finally {
            close(bufferedReader);
            close(inputStreamReader);
            close(inputStream);
        }
    }

    public void readL2StrNoBuffer(String pathFile, IOListener ioListener) {
        FileInputStream fileInputStream = null;
        try {
            fileInputStream = new FileInputStream(pathFile);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            ioListener.onFail(e.getMessage());
            return;
        }
        readL2StrNoBuffer(fileInputStream, ioListener);
    }

    /**
     * 一行一行地读,不拼接
     *
     * @param inputStream
     * @param ioListener
     */
    public void readL2StrNoBuffer(InputStream inputStream, IOListener ioListener) {

        BufferedReader bufferedReader = null;
        InputStreamReader inputStreamReader = null;
        try {
            inputStreamReader = new InputStreamReader(inputStream, encodeType);
            bufferedReader = new BufferedReader(inputStreamReader);

            long current = 0;

            String str;
            while (isRunning && (str = bufferedReader.readLine()) != null) {
                current += str.length();

                ioListener.onLoading(str, current, contentLength);

            }

            //中断
            if ((str = bufferedReader.readLine()) != null) {
                ioListener.onInterrupted();
            } else {

                ioListener.onCompleted("");

            }

        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            ioListener.onFail(e.getMessage());
        } catch (IOException e) {
            e.printStackTrace();
            ioListener.onFail(e.getMessage());

        } finally {
            close(bufferedReader);
            close(inputStreamReader);
            close(inputStream);
        }
    }

    public void readL_N2String(String pathFile, IOListener ioListener) {
        FileInputStream fileInputStream = null;
        try {
            fileInputStream = new FileInputStream(pathFile);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            ioListener.onFail(e.getMessage());
            return;
        }
        readL_N2String(fileInputStream, ioListener);
    }

    /**
     * 一行一行地读,\n拼接
     *
     * @param inputStream
     * @param ioListener
     */
    public void readL_N2String(InputStream inputStream, IOListener ioListener) {

        BufferedReader bufferedReader = null;
        InputStreamReader inputStreamReader = null;
        try {
            inputStreamReader = new InputStreamReader(inputStream, encodeType);
            bufferedReader = new BufferedReader(inputStreamReader);

            StringBuilder sb = new StringBuilder();
            long current = 0;

            String str;
            while (isRunning && (str = bufferedReader.readLine()) != null) {
                sb.append(str);
                sb.append("\n");
                current += str.length();

                ioListener.onLoading(str, current, contentLength);

            }

            //中断
            if ((str = bufferedReader.readLine()) != null) {
                ioListener.onInterrupted();
            } else {

                ioListener.onCompleted(sb.toString());

            }

        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            ioListener.onFail(e.getMessage());
        } catch (IOException e) {
            e.printStackTrace();
            ioListener.onFail(e.getMessage());

        } finally {
            close(bufferedReader);
            close(inputStreamReader);
            close(inputStream);
        }
    }

    /**
     * 读取到文件
     *
     * @param inputStream
     * @param outputStream
     * @param ioListener
     */
    public void read2File(InputStream inputStream, OutputStream outputStream, IOListener ioListener) {

        try {

            byte[] buffer = new byte[1024];
            int len = 0;
            long current = 0;

            while (isRunning && (len = inputStream.read(buffer)) != -1) {

                outputStream.write(buffer, 0, len);
                current += len;
                ioListener.onLoading(new String(buffer), current, contentLength);
            }

            outputStream.flush();
            //中断
            if (len != -1) {
                ioListener.onInterrupted();
            } else {

                ioListener.onCompleted(null);

            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            close(outputStream);
            close(inputStream);
        }
    }

    /**
     * 将str写入文件
     */
    public void writeStr2File(String str, String pathFile, IOListener ioListener) {
        BufferedWriter bufferedWriter = null;
        OutputStreamWriter outputStreamWriter = null;
        OutputStream outputStream = null;
        try {
            outputStream = new FileOutputStream(pathFile);
            outputStreamWriter = new OutputStreamWriter(outputStream);
            bufferedWriter = new BufferedWriter(outputStreamWriter);
            bufferedWriter.write(str);
            ioListener.onCompleted("");
        } catch (IOException e) {
            e.printStackTrace();
            ioListener.onFail(e.getMessage());
        } finally {
            close(bufferedWriter);
            close(outputStreamWriter);
            close(outputStream);
        }
    }

    /**
     * 将str写入文件,同步操作,独占锁
     */
    public void writeStr2ReplaceFileSync(String str, String pathFile, IOListener ioListener) {
        File file;
        try {
            file = FileUtils.createFile(pathFile);
        } catch (IOException e) {
            e.printStackTrace();
            ioListener.onFail("文件创建失败,请检查路径是否合法以及读写权限");
            return;
        }
        FileOutputStream fileOutputStream = null;
        FileChannel fileChannel = null;
        FileLock fileLock = null;//文件锁
        try {

            /**
             * 写文件
             */
            fileOutputStream = new FileOutputStream(file);
            fileChannel = fileOutputStream.getChannel();
            while (true) {
                try {
                    fileLock = fileChannel.tryLock();//独占锁
                    break;
                } catch (Exception e) {
                    System.out.println("有其他线程正在操作该文件,当前线程" + Thread.currentThread().getName());
                }
            }
            if (fileLock != null) {
                int len = 0;
                long current = file.length();
                if (isRunning ) {
                    fileChannel.write(ByteBuffer.wrap(str.getBytes()));
                    current += len;
                    LogUtils.log("当前线程" + Thread.currentThread().getName());
                    ioListener.onLoading(str.getBytes(), current, str.length());
                }else {
                    //中断
                    ioListener.onInterrupted();
                }
                if (fileLock != null && fileLock.isValid()) {
                    LogUtils.log("release-write-lock");
                    fileLock.release();
                }
                close(fileChannel);
                close(fileOutputStream);
                if (file.length() == str.getBytes().length) {
                    ioListener.onCompleted(file);
                }
            }

        } catch (IOException e) {
            e.printStackTrace();
            ioListener.onFail(e.getMessage());
        } finally {
        }
    }

  

}

写文件使用示例

 new IOUtils().writeStr2ReplaceFileSync(jsonObjectOld.toJSONString(), Constants.PATH_GAME_JSON, new IOListener() {
                            @Override
                            public void onCompleted(Object result) {
                                
                            }

                            @Override
                            public void onLoading(Object readedPart, long current, long length) {

                            }

                            @Override
                            public void onInterrupted() {

                            }

                            @Override
                            public void onFail(String errorMsg) {

                            }
                        });

读文件使用示例

new IOUtils().read2StrSync(Constants.PATH_CONFIG_APPLICATION_JSON, new IOListener() {
            @Override
            public void onCompleted(String result) {
                
            }

            @Override
            public void onLoading(String readedPart, long current, long length) {

            }

            @Override
            public void onInterrupted() {

            }

            @Override
            public void onFail(String errorMsg) {

            }
        });

各位老铁有问题欢迎及时联系、指正、批评、撕逼

Github:
https://github.com/AnJiaoDe
简书:
https://www.jianshu.com/u/b8159d455c69

微信公众号
JAVA多线程读写文件如何做到线程安全?(文件锁,FileChannel)_第2张图片

QQ群
JAVA多线程读写文件如何做到线程安全?(文件锁,FileChannel)_第3张图片

你可能感兴趣的:(JAVA多线程)