当多个线程试图在JAVA中使用NIO追加内容时,是否需要锁定文件?
首先我创建了一个空文件,然后我调用了一些线程来搜索数据库并获取结果内容,然后附加到文件中。结果内容为字符串类型,可能为20M。每个线程应该一次写入一个文件。我已经测试了很多次,我发现没有必要锁定。是这样吗?该示例的总行数为1000。何时需要添加写锁才能对文件进行操作当多个线程试图在JAVA中使用NIO追加内容时,是否需要锁定文件?,java,multithreading,java-8,java.util.concurrent,threadpoolexecutor,Java,Multithreading,Java 8,Java.util.concurrent,Threadpoolexecutor,首先我创建了一个空文件,然后我调用了一些线程来搜索数据库并获取结果内容,然后附加到文件中。结果内容为字符串类型,可能为20M。每个线程应该一次写入一个文件。我已经测试了很多次,我发现没有必要锁定。是这样吗?该示例的总行数为1000。何时需要添加写锁才能对文件进行操作 String currentName = "test.txt"; final String LINE_SEPARATOR = System.getProperty("line.separator"); Thr
String currentName = "test.txt";
final String LINE_SEPARATOR = System.getProperty("line.separator");
ThreadPoolExecutor pool = new ThreadPoolExecutor(
10, 100, 10, TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>());
for (int i = 0; i < 500; i++) {
pool.execute(() -> {
try {
appendFileByFilesWrite(currentName, "abc" +
ThreadLocalRandom.current().nextInt(1000) + LINE_SEPARATOR);
} catch (IOException e) {
e.printStackTrace();
}
});
}
IntStream.range(0, 500).<Runnable>mapToObj(a -> () -> {
try {
appendFileByFilesWrite( currentName,
"def" + ThreadLocalRandom.current().nextInt(1000) +
LINE_SEPARATOR);
} catch (IOException e) {
e.printStackTrace();
}
}).forEach(pool::execute);
pool.shutdown();
答案是:永远
你的测试对你有用。马上今天也许在满月的时候,它不会。也许如果你买了一台新电脑,或者你的操作系统供应商更新了,或者JDK更新了,或者你正在winamp中播放一首布兰妮·斯皮尔斯的歌,那就不会了
规范说,在多个步骤中抹去写操作是合法的,并且SOO.APPEND的行为在这一点上是未定义的。如果同时编写“Hello”和“World”,则文件可能最终包含“helworlld”。可能不会。但它可以
一般来说,并发中的bug很难测试,有时甚至不可能测试。不会让它成为一个小虫子;大多数情况下,你会得到大量的bug报告,并且你会回答“无法复制”所有的bug报告。这不是个好地方
如果您想观察实际问题,最有可能的是,您应该在writer中编写非常长的字符串;其目的是最终得到实际的低级磁盘命令,该命令涉及多个分离出的块。即使这样,也不能保证您会观察到问题。然而,缺乏证据并不是缺乏证据。答案是:永远
你的测试对你有用。马上今天也许在满月的时候,它不会。也许如果你买了一台新电脑,或者你的操作系统供应商更新了,或者JDK更新了,或者你正在winamp中播放一首布兰妮·斯皮尔斯的歌,那就不会了
规范说,在多个步骤中抹去写操作是合法的,并且SOO.APPEND的行为在这一点上是未定义的。如果同时编写“Hello”和“World”,则文件可能最终包含“helworlld”。可能不会。但它可以
一般来说,并发中的bug很难测试,有时甚至不可能测试。不会让它成为一个小虫子;大多数情况下,你会得到大量的bug报告,并且你会回答“无法复制”所有的bug报告。这不是个好地方
如果您想观察实际问题,最有可能的是,您应该在writer中编写非常长的字符串;其目的是最终得到实际的低级磁盘命令,该命令涉及多个分离出的块。即使这样,也不能保证您会观察到问题。然而,缺少证据并不是缺少证据。您可以使用fileLock,也可以将synchronized添加到方法中
while (true) {
try {
lock = fc.lock();
break;
} catch (OverlappingFileLockException e) {
Thread.sleep(1 * 1000);
}
}
appendFileByFilesWrite( fileName, fileContent) ;
或者像这样改变:
public synchronized static void appendFileByFilesWrite(String fileName,String fileContent) throws IOException {
Files.write(Paths.get(fileName), fileContent.getBytes(),StandardOpenOption.APPEND);
}
您可以使用fileLock,也可以将synchronized添加到方法中
while (true) {
try {
lock = fc.lock();
break;
} catch (OverlappingFileLockException e) {
Thread.sleep(1 * 1000);
}
}
appendFileByFilesWrite( fileName, fileContent) ;
或者像这样改变:
public synchronized static void appendFileByFilesWrite(String fileName,String fileContent) throws IOException {
Files.write(Paths.get(fileName), fileContent.getBytes(),StandardOpenOption.APPEND);
}
当我需要锁定文件时,我使用这个类。它允许跨多个JVM和多个线程进行读写锁定
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.RandomAccessFile;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.util.Date;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import com.lfp.joe.core.process.CentralExecutor;
public class FileLocks {
private static final String WRITE_MODE = "rws";
private static final String READ_MODE = "r";
private static final Map<String, LockContext> JVM_LOCK_MAP = new ConcurrentHashMap<>();
private FileLocks() {
}
public static <X> X read(File file, ReadAccessor<X> accessor) throws IOException {
return access(file, false, fc -> {
try (var is = Channels.newInputStream(fc);) {
return accessor.read(fc, is);
}
});
}
public static void write(File file, WriterAccessor accessor) throws IOException {
access(file, true, fc -> {
try (var os = Channels.newOutputStream(fc);) {
accessor.write(fc, os);
}
return null;
});
}
public static <X> X access(File file, boolean write, FileChannelAccessor<X> accessor)
throws FileNotFoundException, IOException {
Objects.requireNonNull(file);
Objects.requireNonNull(accessor);
String path = file.getAbsolutePath();
var lockContext = JVM_LOCK_MAP.compute(path, (k, v) -> {
if (v == null)
v = new LockContext();
v.incrementAndGetThreadCount();
return v;
});
var jvmLock = write ? lockContext.getAndLockWrite() : lockContext.getAndLockRead();
try (var randomAccessFile = new RandomAccessFile(file, write ? WRITE_MODE : READ_MODE);
var fileChannel = randomAccessFile.getChannel();) {
var fileLock = write ? fileChannel.lock() : null;
try {
return accessor.access(fileChannel);
} finally {
if (fileLock != null && fileLock.isValid())
fileLock.close();
}
} finally {
jvmLock.unlock();
JVM_LOCK_MAP.compute(path, (k, v) -> {
if (v == null)
return null;
var threadCount = v.decrementAndGetThreadCount();
if (threadCount <= 0)
return null;
return v;
});
}
}
public static interface FileChannelAccessor<X> {
X access(FileChannel fileChannel) throws IOException;
}
public static interface ReadAccessor<X> {
X read(FileChannel fileChannel, InputStream inputStream) throws IOException;
}
public static interface WriterAccessor {
void write(FileChannel fileChannel, OutputStream outputStream) throws IOException;
}
private static class LockContext {
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
private long threadCount = 0;
public long incrementAndGetThreadCount() {
threadCount++;
return threadCount;
}
public long decrementAndGetThreadCount() {
threadCount--;
return threadCount;
}
public Lock getAndLockWrite() {
var lock = rwLock.writeLock();
lock.lock();
return lock;
}
public Lock getAndLockRead() {
var lock = rwLock.readLock();
lock.lock();
return lock;
}
}
}
并阅读:
File file = new File("test/lock-test.txt")
var lines = FileLocks.read(file, (fileChannel, inputStream) -> {
try (var br = new BufferedReader(new InputStreamReader(inputStream));) {
return br.lines().collect(Collectors.toList());
}
});
当我需要锁定文件时,我使用这个类。它允许跨多个JVM和多个线程进行读写锁定
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.RandomAccessFile;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.util.Date;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import com.lfp.joe.core.process.CentralExecutor;
public class FileLocks {
private static final String WRITE_MODE = "rws";
private static final String READ_MODE = "r";
private static final Map<String, LockContext> JVM_LOCK_MAP = new ConcurrentHashMap<>();
private FileLocks() {
}
public static <X> X read(File file, ReadAccessor<X> accessor) throws IOException {
return access(file, false, fc -> {
try (var is = Channels.newInputStream(fc);) {
return accessor.read(fc, is);
}
});
}
public static void write(File file, WriterAccessor accessor) throws IOException {
access(file, true, fc -> {
try (var os = Channels.newOutputStream(fc);) {
accessor.write(fc, os);
}
return null;
});
}
public static <X> X access(File file, boolean write, FileChannelAccessor<X> accessor)
throws FileNotFoundException, IOException {
Objects.requireNonNull(file);
Objects.requireNonNull(accessor);
String path = file.getAbsolutePath();
var lockContext = JVM_LOCK_MAP.compute(path, (k, v) -> {
if (v == null)
v = new LockContext();
v.incrementAndGetThreadCount();
return v;
});
var jvmLock = write ? lockContext.getAndLockWrite() : lockContext.getAndLockRead();
try (var randomAccessFile = new RandomAccessFile(file, write ? WRITE_MODE : READ_MODE);
var fileChannel = randomAccessFile.getChannel();) {
var fileLock = write ? fileChannel.lock() : null;
try {
return accessor.access(fileChannel);
} finally {
if (fileLock != null && fileLock.isValid())
fileLock.close();
}
} finally {
jvmLock.unlock();
JVM_LOCK_MAP.compute(path, (k, v) -> {
if (v == null)
return null;
var threadCount = v.decrementAndGetThreadCount();
if (threadCount <= 0)
return null;
return v;
});
}
}
public static interface FileChannelAccessor<X> {
X access(FileChannel fileChannel) throws IOException;
}
public static interface ReadAccessor<X> {
X read(FileChannel fileChannel, InputStream inputStream) throws IOException;
}
public static interface WriterAccessor {
void write(FileChannel fileChannel, OutputStream outputStream) throws IOException;
}
private static class LockContext {
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
private long threadCount = 0;
public long incrementAndGetThreadCount() {
threadCount++;
return threadCount;
}
public long decrementAndGetThreadCount() {
threadCount--;
return threadCount;
}
public Lock getAndLockWrite() {
var lock = rwLock.writeLock();
lock.lock();
return lock;
}
public Lock getAndLockRead() {
var lock = rwLock.readLock();
lock.lock();
return lock;
}
}
}
并阅读:
File file = new File("test/lock-test.txt")
var lines = FileLocks.read(file, (fileChannel, inputStream) -> {
try (var br = new BufferedReader(new InputStreamReader(inputStream));) {
return br.lines().collect(Collectors.toList());
}
});
基本上答案:基本上答案:…而且使用单个编写器会更有效。如何使用单个编写器?我的方法是静态的。你能给我一个例子吗@Holger@flower当您预先创建FileWriter时,AppendFileByFileWrite方法将简化为一个简单的writer.writestring,换句话说,您根本不需要静态方法。@Holger,我将其作为工具类编写,因此当我在另一个类中使用它时,我不需要创建实例。@flower工具本身应该是有用的,而不是目的。创建一个工具,使简单的call writer.writestring变得更加复杂,同时产生您没有遇到的问题,这是毫无意义的。我不知道您为什么如此痴迷于“需要创建实例”,尤其是对于I/O操作,但当然,您的工具方法创建的对象比在整个操作过程中保留编写器的方法多。它从fileContent.getBytes的结果开始,然后是Files.write…并没有神奇的效果。它会在每次调用时创建一个OuputStream…而且使用一个编写器会更有效。如何使用一个编写器?我的方法是静态的。你能给我一个例子吗@Holger@flower当您预先创建FileWriter时,AppendFileByFileWrite方法将简化为一个简单的writer.writestring,换句话说,您根本不需要静态方法。@Holger,我将其作为工具类编写,因此当我在另一个类中使用它时,我不需要创建实例。@flower工具本身应该是有用的,而不是目的。创建一个工具,使简单的call writer.writestring变得更加复杂,同时产生您没有遇到的问题 没有,就没有意义。我不知道您为什么如此痴迷于“需要创建实例”,尤其是对于I/O操作,但当然,您的工具方法创建的对象比在整个操作过程中保留编写器的方法多。它从fileContent.getBytes的结果开始,然后是Files.write…并没有神奇的效果。它在每次调用时创建一个输出流。