簡體   English   中英

當多個線程嘗試append JAVA中使用NIO的內容時是否需要鎖定文件?

[英]Is it necessary lock the file when multiple threads try to append the content using NIO in JAVA?

起初我創建了一個空文件,然后我調用了一些線程來搜索數據庫並獲取結果內容,然后 append 到該文件。 結果內容為String類型,可能為20M。 每個線程應該一次寫入一個文件。 我測試了很多次,發現沒必要鎖。 那正確嗎? 示例的總行數為 1000 行。什么時候需要添加寫鎖才能對文件進行操作?

    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(); 

這是方法:

public static void appendFileByFilesWrite(String fileName,String fileContent) throws IOException {
    Files.write(Paths.get(fileName), fileContent.getBytes(),StandardOpenOption.APPEND);
}

答案是:總是

你的測試適合你。 馬上。 今天。 也許在滿月期間,它不會。 也許如果你買了一台新電腦,或者你的操作系統供應商更新,或者 JDK 更新,或者你在你的 winamp 中播放一首布蘭妮斯皮爾斯歌曲,它不會。

規范說,在多個步驟中抹去寫入是合法的,並且 SOO.APPEND 的行為在這一點上是未定義的。 可能如果你同時寫 'Hello' 和 'World',文件可能最終會包含 'HelWorllod'。 它可能不會。 但它可以。

通常,並發錯誤很難(有時實際上是不可能的)測試。 不會減少它的錯誤; 大多數情況下,您最終會收到大量錯誤報告,並且您對所有這些都回答“無法重現”。 這不是一個好地方。

如果您想觀察實際問題,很可能應該在編寫器中編寫極長的字符串; 目的是最終得到涉及多個分離塊的實際低級磁盤命令。 即使那樣,也不能保證您會發現問題。 然而,缺乏證據並不是不存在的證據。

當我需要鎖定文件時,我使用這個 class。 它允許跨多個 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");
FileLocks.write(file, (fileChannel, outputStream) -> {
    try (var bw = new BufferedWriter(new OutputStreamWriter(outputStream));) {
        bw.append("cool beans " + new Date().getTime());
    }
});

並閱讀:

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());
    }
});

您可以使用 fileLock 或只是將同步添加到該方法。

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);
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM