【RandomAccessFile类多线程场景踩坑】

背景

本来是想开发一个满足多线程场景需求的对指定内容写入log文件的工具类,同时拥有缓存机制,只有待写入内容超过缓存空间大小时才会进行写入。

但是在多线程测试时却发现,在多线程场景下,会存在文件内容与写入内容混乱的问题
如下:85列nnnn后面会写入tttt。
在这里插入图片描述

核心实现方法

//将指定内容写入指定文件,同时支持内容缓存,减少文件读写操作
public class FileUtil {
    public final static String filePath = "D:/TestFile/";
    private volatile Map<String, StringBuffer> bufferMap = new ConcurrentHashMap<>();
    private final static int BUFFER_MAX_LENGTH = 50;

    /**
     * 保存指定字符串到打指定文件中,通过缓存机制优化性能,只有超过缓存区大小才写入文件
     *
     * @param fileName  文件名称
     * @param source
     * @return
     */
    public synchronized boolean saveString2TxtFile(String fileName, String source) {
        boolean result = false;
        StringBuffer currentContent;
        if (bufferMap.containsKey(fileName)) {
            if (bufferMap.get(fileName).toString().endsWith(System.lineSeparator())) {
                currentContent = bufferMap.get(fileName).append(source);
            } else {
                currentContent = bufferMap.get(fileName).append(System.lineSeparator() + source);
            }
        } else {
            currentContent = new StringBuffer(System.lineSeparator() + source);
            bufferMap.put(fileName, currentContent);
        }
        if (currentContent.length() > BUFFER_MAX_LENGTH) {//只有当前缓存区内容大于缓存区最大存储空间才将内容写入log文件中
            result = writeStr2File(fileName, currentContent.toString());
            if (result) {//如果保存失败,则不清除缓存区内存,用于再次清除
                bufferMap.put(fileName, new StringBuffer());
            }
        }
        return result;
    }

有问题的实现

其中有问题的实现如下:writeStr2File1加了synchronized 去修饰,为线程安全的写法。但是实际上在多线程场景下是有串行的或者多余字符的问题

/**
     * 将字符串拼接写入txt文件最后 -通过RandomAccessFile
     *
     * @param fileName
     * @param source
     * @return
     */
    @Deprecated
    private synchronized boolean writeStr2File1(String fileName, String source) {
        String absolutePath = filePath + fileName;
        File file = new File(absolutePath);
        File parentFile = new File(filePath);
        if (!file.exists()) {
            if (!parentFile.exists()) {
                parentFile.mkdirs();
            }
            try {
                file.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
                return false;
            }
        }
        try (RandomAccessFile randomAccessFile = new RandomAccessFile(absolutePath, "rw")) {
            long fileLength = randomAccessFile.length();//文件长度
            randomAccessFile.seek(fileLength);
            randomAccessFile.writeBytes(source);
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }
}

改进后的实现

后面将RandomAccessFile修改成通过FileOutputStream来实现

/**
     * 将字符串拼接写入txt文件最后 -通过FileOutputStream
     * 优选方法
     *
     * @param fileName 文件名称
     * @param source   待写入源文件字符串
     * @return
     */
    private boolean writeStr2File(String fileName, String source) {
        String absolutePath = filePath + fileName;

        File file = new File(absolutePath);
        File parentFile = new File(filePath);
        if (!file.exists()) {
            if (!parentFile.exists()&& parenFile.) {
                parentFile.mkdirs();
            }
            try {
                file.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
                return false;
            }
        }

        FileLock lock = null;
        //true表示是表示是在文件末尾新增
        try (FileChannel channel = new FileOutputStream(absolutePath, true).getChannel()) {
            while (true) {
                try {
                    lock = channel.lock();
                    if (lock != null && lock.isValid()) {
                        break;
                    }
                } catch (Exception e) {
                    Thread.sleep(10);
                }
            }
            channel.write(ByteBuffer.wrap(source.getBytes()));
            lock.release();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return true;
    }

多线程测试用例

public class fileUtilTest {
    String[] printStr = {"kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk", "ttttttttttttttttttttttttttttttttttttttttttttttttttt",
            "wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww", "mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm", "nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn", "jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj"};

    private static CountDownLatch latch=new CountDownLatch(10);
    @Test
    public void saveString2TxtFileTest() throws InterruptedException {
        MutiThread mutiThread=new MutiThread();
        for (int i = 0; i < 10; i++) {
            Thread thread=new Thread(mutiThread);
            thread.start();
        }
        latch.await();
    }

    private class MutiThread implements Runnable{
        @Override
        public void run() {
            FileUtil fileUtil = new FileUtil();
            for (int i = 0; i < 100; i++) {
                String fileName = "test" + (i / 10);
                Date localDate = new Date();
                SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-SSS");
                String currentDateStr = format.format(localDate);
                Random random = new Random();

                String logStr = i +"-"+ Thread.currentThread().getName()+"-"+"-" + fileName + "-" + currentDateStr + "-" + printStr[random.nextInt(5)];
                System.out.println(logStr);
                assert fileUtil.saveString2TxtFile(fileName, logStr);
            }
            latch.countDown();
        }
    }
}

分析

在单线程场景没有发现这个问题,但是在多线程场景却出现了这个问题,具体原因在网上也没有找到相关资源,但是如参考资料所附的链接,这个博主也遇到这个问题,怀疑是RandomRccessFile类本身的原因但是具体原因也未知,本人太菜,也无法分析原因,在此做记录,避免下次踩坑。

参考资料

https://www.jianshu.com/p/9ed36510e51f

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值