Подтвердить что ты не робот

Concurrency RandomAccessFile в Java

Я создаю объект RandomAccessFile для записи в файл (на SSD) несколькими потоками. Каждый поток пытается записать буфер прямого байта в определенную позицию внутри файла, и я гарантирую, что позиция, в которой поток пишет, не будет перекрываться с другим потоком:

file_.getChannel().write(buffer, position);

где file_ - это экземпляр RandomAccessFile, а buffer - буфер с прямым байтом.

Для объекта RandomAccessFile, поскольку я не использую fallocate для выделения файла, а длина файла меняется, будет ли использоваться concurrency основного носителя?

Если это не так, есть ли смысл использовать указанную выше функцию без вызова fallocate при создании файла?

4b9b3361

Ответ 1

Я провел несколько тестов со следующим кодом:

   public class App {
    public static CountDownLatch latch;

    public static void main(String[] args) throws InterruptedException, IOException {
        File f = new File("test.txt");
        RandomAccessFile file = new RandomAccessFile("test.txt", "rw");
        latch = new CountDownLatch(5);
        for (int i = 0; i < 5; i++) {
            Thread t = new Thread(new WritingThread(i, (long) i * 10, file.getChannel()));
            t.start();

        }
        latch.await();
        file.close();
        InputStream fileR = new FileInputStream("test.txt");
        byte[] bytes = IOUtils.toByteArray(fileR);
        for (int i = 0; i < bytes.length; i++) {
            System.out.println(bytes[i]);

        }  
    }

    public static class WritingThread implements Runnable {
        private long startPosition = 0;
        private FileChannel channel;
        private int id;

        public WritingThread(int id, long startPosition, FileChannel channel) {
            super();
            this.startPosition = startPosition;
            this.channel = channel;
            this.id = id;

        }

        private ByteBuffer generateStaticBytes() {
            ByteBuffer buf = ByteBuffer.allocate(10);
            byte[] b = new byte[10];
            for (int i = 0; i < 10; i++) {
                b[i] = (byte) (this.id * 10 + i);

            }
            buf.put(b);
            buf.flip();
            return buf;

        }

        @Override
        public void run() {
            Random r = new Random();
            while (r.nextInt(100) != 50) {
                try {
                    System.out.println("Thread  " + id + " is Writing");
                    this.channel.write(this.generateStaticBytes(), this.startPosition);
                    this.startPosition += 10;
                } catch (IOException e) {
                    e.printStackTrace();

                }
            }
            latch.countDown();
        }
    }
}

До сих пор я видел:

  • Windows 7 (раздел NTFS): выполняется линейно (иначе один поток записывает и когда он закончен, другой запускается)

  • Linux Parrot 4.8.15 (раздел ext4) (дистрибутив на основе Debian) с Linux Kernel 4.8.0: потоки перемежаются во время выполнения

Опять же, как документация говорит:

Файловые каналы безопасны для использования несколькими параллельными потоками. метод close может быть вызван в любое время, как указано в канале интерфейс. Только одна операция, которая включает в себя позицию канала или может изменить размер своего файла, может быть в любой момент; попытки инициировать вторую такую ​​операцию, в то время как первая по-прежнему будет выполняться до тех пор, пока не завершится первая операция. Другие операции, в частности те, которые занимают явное положение, могут продолжаться одновременно; действительно ли они на самом деле зависят от и поэтому не указывается.

Итак, я бы предложил сначала попробовать и посмотреть, будут ли OS развертывать ваш код (возможно, тип файловой системы) для параллельного выполнения вызова FileChannel.write

Изменить. Как указано выше, вышеизложенное не означает, что потоки могут писать одновременно с файлом, это фактически противоположно тому, как вызов write ведет себя в соответствии с контрактом WritableByteChannel, в котором четко указано, что только один поток за один раз может записать в данный файл:

Если один поток инициирует операцию записи по каналу, то любой другой поток, который пытается инициировать другую операцию записи, будет блок до завершения первой операции

Ответ 2

Как указано в документации, и Adonis уже упоминает об этом, запись может выполняться только по одному потоку за раз. Более того, вы не сможете добиться повышения производительности за счет concurreny, более того, вы должны беспокоиться только о производительности, если это актуальная проблема, потому что одновременная запись на диск может ухудшить вашу производительность (возможно, меньше для SSD, чем HDD).

В большинстве случаев основной носитель (SSD, HDD, Network) однопоточен - на самом деле нет такой вещи, как поток на аппаратном уровне, нити - не что иное, как абстракция.

В вашем случае носитель является SSD. В то время как SSD внутренне может одновременно записывать данные на несколько модулей (они могут достигать уровня параллизма, где записи могут быть такими же быстрыми и даже превосходить чтение), внутренние структуры сопоставления являются общим ресурсом и поэтому рассматриваются, особенно при частых обновлениях, таких как одновременные пишет. Тем не менее, обновления этой структуры данных довольно быстрые, и поэтому не о чем беспокоиться, если это не становится проблемой.

Но кроме этого, это только внутренности SSD. Снаружи вы обмениваетесь интерфейсом Serial ATA, таким образом, однобайтовые по времени (фактически пакеты в информационной структуре кадра, FIS). Кроме того, это OS/Filesystem, которая снова имеет, вероятно, конкурирующую структуру данных и/или применяет собственные средства оптимизации, такие как кэширование с записью.

Кроме того, поскольку вы знаете, что такое ваши медиа, вы можете оптимизировать особенно для этого, и SSD действительно быстры, когда один поток пишет большую часть данных.

Таким образом, вместо того, чтобы использовать несколько потоков для записи, вы можете создать большой буфер в памяти (возможно, рассмотреть файл с отображением памяти) и написать одновременно в этот буфер. Сама память не рассматривается, если вы обеспечиваете каждому потоку доступ к собственному адресному пространству буфера. После того, как все потоки выполнены, вы записываете этот один буфер в SSD (не требуется, если используется файл с отображением памяти).

См. также это хорошее резюме о разработке для SSD: Резюме - Что каждый программист должен знать о твердотельных дисках

Точка для предварительного выделения (или, точнее, file_.setLength(), которая соответствует картам ftruncate)) заключается в том, что изменение размера файла может использовать дополнительные циклы, и вы можете избежать этого, Но опять же это может зависеть от ОС/файловой системы.