当前位置:首页 > Java技术 > java 多个线程同时写同一个文件

java 多个线程同时写同一个文件

2022年11月08日 18:10:30Java技术13

话不多说,先直接上代码:

主方法:

import java.util.concurrent.CountDownLatch;

/**
 * @ProjectName: emp_customer
 * @Package: PACKAGE_NAME
 * @ClassName: Test
 * @Author: Administrator
 * @Description: ${description}
 * @Date: 2019/10/11 14:10
 * @Version: 1.0
 */
public class Test {
     public static void main(String args[]){

         //线程数
         int threadSize=4;
         //源文件地址
         String sourcePath = "E:\\1\\4.txt";
         //目标文件地址
         String destnationPath = "E:\\2\\4.txt";
         //
         CountDownLatch latch = new CountDownLatch(threadSize);
         MultiDownloadFileThread m = new MultiDownloadFileThread(threadSize, sourcePath, destnationPath, latch);
         long startTime = System.currentTimeMillis();
         try {
             m.excute();
             latch.await();
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
         long endTime = System.currentTimeMillis();
         System.out.println("全部下载结束,共耗时" + (endTime - startTime) / 1000 + "s");
     }

}

 

线程类:

import java.io.*;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.util.concurrent.CountDownLatch;

/**
 * @ProjectName: emp_customer
 * @Package: PACKAGE_NAME
 * @ClassName: MultiDownloadFileThread
 * @Author: Administrator
 * @Description: ${description}
 * @Date: 2019/10/11 15:03
 * @Version: 1.0
 */
public class MultiDownloadFileThread {

    private int threadCount;
    private String sourcePath;
    private String targetPath;
    private CountDownLatch latch;

    public MultiDownloadFileThread(int threadCount, String sourcePath, String targetPath, CountDownLatch latch) {
        this.threadCount = threadCount;
        this.sourcePath = sourcePath;
        this.targetPath = targetPath;
        this.latch = latch;
    }

    public void excute() {
        File file = new File(sourcePath);
        int fileLength = (int) file.length();
        //分割文件
        int blockSize = fileLength / threadCount;
        for (int i = 1; i <= threadCount; i++) {
            //第一个线程下载的开始位置
            int startIndex = (i - 1) * blockSize;
            int endIndex = startIndex + blockSize - 1;
            if (i == threadCount) {
                //最后一个线程下载的长度稍微长一点
                endIndex = fileLength;
            }
            System.out.println("线程" + i + "下载:" + startIndex + "字节~" + endIndex + "字节");
            new DownLoadThread(i, startIndex, endIndex).start();
        }
    }


    public class DownLoadThread extends Thread {
        private int i;
        private int startIndex;
        private int endIndex;

        public DownLoadThread(int i, int startIndex, int endIndex) {
            this.i = i;
            this.startIndex = startIndex;
            this.endIndex = endIndex;
        }

        @Override
        public void run() {
            File file = new File(sourcePath);
            FileInputStream in = null;
            RandomAccessFile raFile = null;
            FileChannel fcin = null;
            FileLock flin = null;
            try {
                in = new FileInputStream(file);
                in.skip(startIndex);
                //给要写的文件加锁
                raFile = new RandomAccessFile(targetPath, "rwd");
                fcin =raFile.getChannel();
                while(true){
                    try {
                        flin = fcin.tryLock();
                        break;
                    } catch (Exception e) {
                        System.out.println("有其他线程正在操作该文件,当前线程休眠1000毫秒,当前进入的线程为:"+i);
                        sleep(1000);
                    }
                }
                //随机写文件的时候从哪个位置开始写
                raFile.seek(startIndex);
                int len = 0;
                byte[] arr = new byte[1024];
                //获取文件片段长度
                int segLength = endIndex - startIndex + 1;
                while ((len = in.read(arr)) != -1) {
                    if (segLength > len) {
                        segLength = segLength - len;
                        raFile.write(arr, 0, len);
                    } else {
                        raFile.write(arr, 0, segLength);
                        break;
                    }
                }
                System.out.println("线程" + i + "下载完毕");
                //计数值减一
                latch.countDown();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException | InterruptedException e) {
                e.printStackTrace();
            } finally {
                try {
                    if (in != null) {
                        in.close();
                    }
                    if (raFile != null) {
                        raFile.close();
                    }

                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

运行结果:

java 多个线程同时写同一个文件 _ JavaClub全栈架构师技术笔记

 

涉及到的相关知识点:

1.CountDownLatch 

2.RandomAccessFile

3.FileLock

下面我们具体讲解下

一、FileLock :文件锁

FileLock是java 1.4 版本后出现的一个类,它可以通过对一个可写文件(w)加锁,保证同时只有一个进程可以拿到文件的锁,这个进程从而可以对文件做访问;而其它拿不到锁的进程要么选择被挂起等待,要么选择去做一些其它的事情, 这样的机制保证了众进程可以顺序访问该文件。

1. 概念

  • 共享锁: 共享读操作,但只能一个写(读可以同时,但写不能)。共享锁防止其他正在运行的程序获得重复的独占锁,但是允许他们获得重复的共享锁。
  • 独占锁: 只有一个读或一个写(读和写都不能同时)。独占锁防止其他程序获得任何类型的锁。

2. lock()和tryLock()的区别:

lock()阻塞的方法,锁定范围可以随着文件的增大而增加。无参lock()默认为独占锁;有参lock(0L, Long.MAX_VALUE, true)为共享锁。
tryLock()非阻塞,当未获得锁时,返回null.
3. FileLock的生命周期:在调用FileLock.release(),或者Channel.close(),或者JVM关闭

4. FileLock是线程安全的
 

二、RandomAccessFile

java除了File类之外,还提供了专门处理文件的类,即RandomAccessFile(随机访问文件)类。该类是Java语言中功能最为丰富的文件访问类,它提供了众多的文件访问方法。RandomAccessFile类支持“随机访问”方式,这里“随机”是指可以跳转到文件的任意位置处读写数据。在访问一个文件的时候,不必把文件从头读到尾,而是希望像访问一个数据库一样“随心所欲”地访问一个文件的某个部分,这时使用RandomAccessFile类就是最佳选择。

RandomAccessFile对象类有个位置指示器,指向当前读写处的位置,当前读写n个字节后,文件指示器将指向这n个字节后面的下一个字节处。刚打开文件时,文件指示器指向文件的开头处,可以移动文件指示器到新的位置,随后的读写操作将从新的位置开始。RandomAccessFile类在数据等长记录格式文件的随机(相对顺序而言)读取时有很大的优势,但该类仅限于操作文件,不能访问其他的I/O设备,如网络、内存映像等。RandomAccessFile类的构造方法如下所示:

RandomAccessFile(File file ,  String mode)
//创建随机存储文件流,文件属性由参数File对象指定

RandomAccessFile(String name ,  String mode)
//创建随机存储文件流,文件名由参数name指定

这两个构造方法均涉及到一个String类型的参数mode,它决定随机存储文件流的操作模式,其中mode值及对应的含义如下:

“r”:以只读的方式打开,调用该对象的任何write(写)方法都会导致IOException异常
“rw”:以读、写方式打开,支持文件的读取或写入。若文件不存在,则创建之。
“rws”:以读、写方式打开,与“rw”不同的是,还要对文件内容的每次更新都同步更新到潜在的存储设备中去。这里的“s”表示synchronous(同步)的意思
“rwd”:以读、写方式打开,与“rw”不同的是,还要对文件内容的每次更新都同步更新到潜在的存储设备中去。使用“rwd”模式仅要求将文件的内容更新到存储设备中,而使用“rws”模式除了更新文件的内容,还要更新文件的元数据(metadata),因此至少要求1次低级别的I/O操作

 

三、CountDownLatch

1.概念

  • countDownLatch这个类使一个线程等待其他线程各自执行完毕后再执行。
  • 是通过一个计数器来实现的,计数器的初始值是线程的数量。每当一个线程执行完毕后,计数器的值就-1,当计数器的值为0时,表示所有线程都执行完毕,然后在闭锁上等待的线程就可以恢复工作了。

2.源码

  • countDownLatch类中只提供了一个构造器:
//参数count为计数值
public CountDownLatch(int count) {  };  
  • 类中有三个方法是最重要的:
//调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行
public void await() throws InterruptedException { };   
//和await()类似,只不过等待一定的时间后count值还没变为0的话就会继续执行
public boolean await(long timeout, TimeUnit unit) throws InterruptedException { };  
//将count值减1
public void countDown() { };  

假如在我们的代码里面,我们把main方法里面的

latch.await();

注释掉

如下所示:

java 多个线程同时写同一个文件 _ JavaClub全栈架构师技术笔记

我们可以看到跟之前的输出结果相比,我们的主方法里面输出的:全部下载结束的输出信息,已经打印到我们执行文件下载的线程输出信息的前面了,说明主线程先执行完。这从而说明,await() 方法具有阻塞作用

 我们在把latch.await();放开,把文件下载线程里的latch.countDown();注释掉,

如下:

java 多个线程同时写同一个文件 _ JavaClub全栈架构师技术笔记

我们可以看到,主程序里的的输出;全部下载结束的输出信息,一直未输出,程序也一直未结束,由此可得,countDown() 方法具有唤醒阻塞线程的作用。

那么如何让 CountdownLatch 尽早结束

假如我们的程序执行到countDown()之前就抛出异常,这就可能导致一整情况,CountdownLatch 计数永远不会达到零并且 await() 永远不会终止。

java 多个线程同时写同一个文件 _ JavaClub全栈架构师技术笔记

java 多个线程同时写同一个文件 _ JavaClub全栈架构师技术笔记

为了解决这个问题,我们在调用 await() 时添加一个超时参数。

java 多个线程同时写同一个文件 _ JavaClub全栈架构师技术笔记

java 多个线程同时写同一个文件 _ JavaClub全栈架构师技术笔记

 

CountDownLatch总结:

    1、CountDownLatch end = new CountDownLatch(N); //构造对象时候 需要传入参数N

  2、end.await()  能够阻塞线程 直到调用N次end.countDown() 方法才释放线程,最好设置超时参数

  3、end.countDown() 可以在多个线程中调用  计算调用次数是所有线程调用次数的总和

 

对于,本demo而言,加不加文件锁的意义不大,因为在进入线程写的时候,就已经告诉单个线程需要写的内容是哪一块到哪一块,不加锁,也会正常写入,切经本人测试无误,但若是对同一个文件,即要写,又要读话,就必须加锁,不然程序执行可能不完整,具体情况可以查看下面的这个博客:https://blog.csdn.net/gxy3509394/article/details/7435993

作者:bird_tp
来源链接:https://blog.csdn.net/bird_tp/article/details/102504833

版权声明:
1、JavaClub(https://www.javaclub.cn)以学习交流为目的,由作者投稿、网友推荐和小编整理收藏优秀的IT技术及相关内容,包括但不限于文字、图片、音频、视频、软件、程序等,其均来自互联网,本站不享有版权,版权归原作者所有。

2、本站提供的内容仅用于个人学习、研究或欣赏,以及其他非商业性或非盈利性用途,但同时应遵守著作权法及其他相关法律的规定,不得侵犯相关权利人及本网站的合法权利。
3、本网站内容原作者如不愿意在本网站刊登内容,请及时通知本站(javaclubcn@163.com),我们将第一时间核实后及时予以删除。


本文链接:https://www.javaclub.cn/java/68502.html

标签: Java
分享给朋友:

“java 多个线程同时写同一个文件” 的相关文章

枚举法 之Java实现凑硬币

问题? 如何利用1元五元十元凑硬币 Scanner in=new Scanner(System.in); int amout ; amout=in.nextInt(); for(int one =0;one<=amout;one+...

Java实现阶乘运算

n!=123*…n 学习编程就是要了解从问题到程序是如何实现的 Scanner in=new Scanner(System.in); int n ; n=in.nextInt(); // int i=1; int factor=1;...

java之整数的分解可以理解为倒序输出

Scanner in=new Scanner(System.in); int number ; number=in.nextInt(); int result=0; do{ int diget=number%10;...

在JAVA 中将堆与栈分开的原因

栈是运行时的单位,而堆是存储的单位。 栈解决程序的运行问题,即程序如何执行,或者说如何处理数据;堆解决的是数据存储的问题,即数据怎么 放、放在哪儿。 注意:在Java中一个线程就会相应有一个线程栈与之对应 栈因为是运行单位,因此里面存储的信息都是跟...

动车上的书摘-java对象流与序列化

动车上的书摘-java对象流与序列化

动车上的书摘-java对象流与序列化摘要: 摘要: 原创出处: http://www.cnblogs.com/Alandre/ 泥沙砖瓦浆木匠 希望转载,保留摘要,谢谢!钢笔不限贵便宜,书法是来自心对手的交流。-泥沙砖瓦浆木匠 一.对象序列化当需要存储相同类型的数据,选...

Java 基础:hashCode方法

Java 基础:hashCode方法

Writer:BYSocket(泥沙砖瓦浆木匠) 微博:BYSocket 豆瓣:BYSocket 一、前言     泥瓦匠最近被项目搞的天昏地暗。发现有些要给自己一些目标,关于技术的目标: 专注...

JAVA UUID 生成唯一标识

Writer:BYSocket(泥沙砖瓦浆木匠) 微博:BYSocket 豆瓣:BYSocket Reprint it anywhere u want 需求     项目在设计表的时候,要处理并发多...

java总结文章

java总结文章

java总结文章 原创地址: http://www.cnblogs.com/Alandre/ (泥沙砖瓦浆木匠),需要转载的,保留下! Thanks Talk is cheap. Show me the...

程序人生|五年java 含泪总结,建议小白看

程序人生|五年java 含泪总结,建议小白看

关注公众号“AI码师”领取2021最新面试资料和java学习路线 最近在网上看到很多人都在写自己的工作总结,我在想我要不要也跟风一下,思考之后,于是就有了这篇文章 这篇文章主要讲述了自己的一些工作经验,和一些在学习j...

实现一个Java五子棋

实现一个Java五子棋

五子棋手把手教你写: 写在前面的话: 回想起从前初学代码的五子棋简直写的不像样子。今天闲来无事就写了个五子棋的小程序。 如果有需要可以从github上下载:github地址:https://github.com/GodofOrange/gobang.git 一来...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。