背景
本来是想开发一个满足多线程场景需求的对指定内容写入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