使用多线程往同一个文件写入数据的线程安全的例子(java实现)

本文给出了一个如何利用java提供的类MappedByteBuffer对文件进行并发写入的例子。具体实现思路就举个列子说明吧: 假设有10000个字节需要写入某个文件,为了加快写入速度,可以开启2个线程,第一个线程将前5000个字节写入文件0~4999的位置,另外一个线程将后5000个字节写入文件5000~9999的位置,等2个线程都写入完成后。我们就成功的将10000个字节完整的写入到了文件中。

值得一提的是,线程在写入过程中是会对自己所占用的那个文件区域枷锁的。各个线程加锁的区域互不重叠,否则会报OverlappingFileLockException异常

这个例子读者可以直接运行main方法查看效果,该类无任何外部依赖,全是jdk自带api。待写入的内容用源码中CONTENS字段表示,该字段记录了欧阳修名作《秋声赋》的完整内容。程序运行结束后可以到D盘查找名为ConcurrentWrite.txt的文件查看写入结果(D://ConcurrentWrite.txt),当然了,前提是你的电脑存在D盘。

程序不是很复杂,直接贴源码吧,关键地方有注释。另外,目标文件ConcurrentWrite.txt在程序运行期间不能被其他外部程序打开,否则写入失败!!!!

package com.lhever.modules.io.test;


import java.io.*;
import java.lang.reflect.Method;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.channels.OverlappingFileLockException;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;

public class MappedByteBufferTest {

    //该字符串的内容将会被多线程写入到文件中
    static String CONTENS = "【作者】欧阳修 【朝代】宋 译文对照\n" +
            "欧阳子方夜读书,闻有声自西南来者,悚然而听之,曰:“异哉!”初淅沥以萧飒,忽奔腾而砰湃,如波涛夜惊,风雨骤至。其触于物也,鏦鏦铮铮,金铁皆鸣;又如赴敌之兵,衔枚疾走,不闻号令,但闻人马之行声。予谓童子:“此何声也?汝出视之。”童子曰:“星月皎洁,明河在天,四无人声,声在树间。”\n" +
            "\n" +
            "予曰:“噫嘻悲哉!此秋声也,胡为而来哉?盖夫秋之为状也:其色惨淡,烟霏云敛;其容清明,天高日晶;其气栗冽,砭人肌骨;其意萧条,山川寂寥。故其为声也,凄凄切切,呼号愤发。丰草绿缛而争茂,佳木葱茏而可悦;草拂之而色变,木遭之而叶脱。其所以摧败零落者,乃其一气之余烈。夫秋,刑官也,于时为阴;又兵象也,于行用金,是谓天地之义气,常以肃杀而为心。天之于物,春生秋实,故其在乐也,商声主西方之音,夷则为七月之律。商,伤也,物既老而悲伤;夷,戮也,物过盛而当杀。”\n" +
            "\n" +
            "“嗟乎!草木无情,有时飘零。人为动物,惟物之灵;百忧感其心,万事劳其形;有动于中,必摇其精。而况思其力之所不及,忧其智之所不能;宜其渥然丹者为槁木,黟然黑者为星星。奈何以非金石之质,欲与草木而争荣?念谁为之戕贼,亦何恨乎秋声!”\n" +
            "\n" +
            "童子莫对,垂头而睡。但闻四壁虫声唧唧,如助予之叹息耶。";


    public static void await(CountDownLatch latch) {
        if (latch == null) {
            return;
        }

        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }



    public static void main(String... args) {

        File file = getByName("D://ConcurrentWrite.txt");
        byte[] bytes = null;
        try {
            bytes = CONTENS.getBytes("UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

        int len = bytes.length;
        System.out.println("len: " + len);
        int concurrentNum = 4;

        if (len < concurrentNum) {
            concurrentNum = 1;
        } else {
            concurrentNum = (len % concurrentNum == 0) ? concurrentNum : (concurrentNum + 1);
        }

        int size = len / concurrentNum;

        List<FileBlockWriter> writers = new ArrayList<FileBlockWriter>();

        CountDownLatch startLatch = new CountDownLatch(1);
        CountDownLatch endLatch = new CountDownLatch(concurrentNum);

        for (int i = 0; i < concurrentNum; i++) {
            FileBlockWriter writer = null;

            int from = i * size;
            int sliceLen = 0;
            System.out.println(from);

            if (i == concurrentNum - 1) {
                sliceLen = bytes.length - i * size;
                System.out.println(sliceLen);
            } else {
                sliceLen = size;
            }

            byte[] slice = new byte[sliceLen];
            System.arraycopy(bytes, from, slice, 0, sliceLen);
            writer = new FileBlockWriter(file, from, sliceLen, slice, startLatch, endLatch);

            writers.add(writer);
        }

        for (FileBlockWriter writer : writers) {
            writer.start();
        }

        //调用countDown后,所有写入线程才真正开始写入工作
        startLatch.countDown();

        //等待所有线程写入完成
        await(endLatch);

        System.out.println("done !!! ");


    }


    private static File getByName(String path) {

        File file = new File(path);

        if (!file.exists()) {
            try {
                file.createNewFile();
            } catch (IOException e) {
                throw new IllegalArgumentException("create file failed", e);
            }
        }

        if (file.isDirectory()) {
            throw new IllegalArgumentException("not a file");
        }

        return file;
    }


    /**
     * 该线程类负责将指定的内容(contents)写入文件(target),
     * 写入的起始位置是:from,往后写的字节数目是:length
     */
    public static class FileBlockWriter extends Thread {

        private File target;
        private int from;
        private int length;
        private byte[] contents;
        private CountDownLatch start;
        private CountDownLatch end;

        public FileBlockWriter(File target, int from, int length, byte[] contents, CountDownLatch start, CountDownLatch end) {
            this.target = target;
            this.from = from;
            this.length = length;
            this.contents = contents;
            this.start = start;
            this.end = end;
        }

        @Override
        public void run() {

            RandomAccessFile randFile = null;
            FileChannel channel = null;
            MappedByteBuffer mbb = null;
            FileLock fileLock = null;

            MappedByteBufferTest.await(start);

            try {

                randFile = new RandomAccessFile(target, "rw");
                channel = randFile.getChannel();
                mbb = channel.map(FileChannel.MapMode.READ_WRITE, from, length);

                fileLock = channel.lock(from, length, true);

                while (fileLock == null || !fileLock.isValid()) {
                    fileLock = channel.lock(from, length, true);
                    System.out.print("锁无效,重复获取");
                }
                mbb.put(contents);
                mbb.force();

            } catch (IOException e) {
                e.printStackTrace();
            } catch (OverlappingFileLockException e) {
                e.printStackTrace();
                throw new IllegalArgumentException("程序设计不合理,加锁区域相互重叠");

            } catch (Exception e) {
                e.printStackTrace();
            } finally {

                release(fileLock);
                forceClose(mbb);
                close(channel, randFile);
            }

            end.countDown();
        }
    }


    private static void release(FileLock fileLock) {
        if (fileLock == null) {
            return;
        }

        try {
            fileLock.release();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void close(Closeable... closeables) {


        if (closeables == null || closeables.length == 0) {
            return;
        }

        for (Closeable closeable : closeables) {
            if (closeable == null) {
                continue;
            }
            try {
                closeable.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 强制关闭MappedByteBuffer
     * @param mbb
     */
    private static void forceClose(MappedByteBuffer mbb) {
        AccessController.doPrivileged(new PrivilegedAction() {
            public Object run() {
                try {
                    Method getCleanerMethod = mbb.getClass().getMethod("cleaner", new Class[0]);
                    getCleanerMethod.setAccessible(true);
                    sun.misc.Cleaner cleaner = (sun.misc.Cleaner)
                            getCleanerMethod.invoke(mbb, new Object[0]);
                    cleaner.clean();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return null;
            }
        });
    }


}
879675643@qq.com  lhever

.---.                                                                         
|   |   .              __.....__   .----.     .----.   __.....__              
|   | .'|          .-''         '.  \    \   /    /.-''         '.            
|   |<  |         /     .-''"'-.  `. '   '. /'   //     .-''"'-.  `. .-,.--.  
|   | | |        /     /________\   \|    |'    //     /________\   \|  .-. | 
|   | | | .'''-. |                  ||    ||    ||                  || |  | | 
|   | | |/.'''. \\    .-------------''.   `'   .'\    .-------------'| |  | | 
|   | |  /    | | \    '-.____...---. \        /  \    '-.____...---.| |  '-  
|   | | |     | |  `.             .'   \      /    `.             .' | |      
'---' | |     | |    `''-...... -'      '----'       `''-...... -'   | |      
      | '.    | '.                                                   |_|      
      '---'   '---'  
  • 3
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
Java中,可以使用线程向同一个文件写入数据。但是需要注意的是,多个线程同时写入同一个文件可能会导致数据错乱和文件损坏的问题。因此,在向同一个文件写入数据时,需要使用线程安全的方式。 以下是一个使用线程向同一个文件写入数据例子: ```java import java.io.FileWriter; import java.io.IOException; public class FileWriterThread extends Thread { private FileWriter writer; public FileWriterThread(FileWriter writer) { this.writer = writer; } @Override public void run() { synchronized (writer) { // 使用 synchronized 关键字保证线程安全 try { writer.write(Thread.currentThread().getName() + ": Hello, World!\n"); System.out.println(Thread.currentThread().getName() + " 写入数据成功!"); } catch (IOException e) { e.printStackTrace(); } } } public static void main(String[] args) { FileWriter writer = null; try { writer = new FileWriter("test.txt", true); // true 表示追加写入 for (int i = 0; i < 10; i++) { new FileWriterThread(writer).start(); // 开启 10 个线程向同一个文件写入数据 } } catch (IOException e) { e.printStackTrace(); } finally { if (writer != null) { try { writer.close(); } catch (IOException e) { e.printStackTrace(); } } } } } ``` 在上面的例子中,我们创建了一个 `FileWriterThread` 类,它继承了 `Thread` 类,并覆盖了 `run()` 方法。在 `run()` 方法中,我们使用 `FileWriter` 类向文件写入数据,并使用 `synchronized` 关键字保证线程安全。 在 `main()` 方法中,我们创建了一个 `FileWriter` 对象,并开启了 10 个线程向同一个文件写入数据。由于我们使用了 `synchronized` 关键字,因此多个线程可以安全地向同一个文件写入数据。 需要注意的是,在使用 `FileWriter` 向文件写入数据时,需要使用 try-catch-finally 语句块来保证资源的正确释放。在上面的例子中,我们在 `finally` 中关闭了 `FileWriter` 对象。
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值