使用多线程往同一个文件写入数据的线程安全的例子(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 writers = new ArrayList();

        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

.---.                                                                         
|   |   .              __.....__   .----.     .----.   __.....__              
|   | .'|          .-''         '.  \    \   /    /.-''         '.            
|   |<  |         /     .-''"'-.  `. '   '. /'   //     .-''"'-.  `. .-,.--.  
|   | | |        /     /________\   \|    |'    //     /________\   \|  .-. | 
|   | | | .'''-. |                  ||    ||    ||                  || |  | | 
|   | | |/.'''. \\    .-------------''.   `'   .'\    .-------------'| |  | | 
|   | |  /    | | \    '-.____...---. \        /  \    '-.____...---.| |  '-  
|   | | |     | |  `.             .'   \      /    `.             .' | |      
'---' | |     | |    `''-...... -'      '----'       `''-...... -'   | |      
      | '.    | '.                                                   |_|      
      '---'   '---'  

你可能感兴趣的:(Java并发编程,多线程写文件,文件锁,NIO,FileChannel)