Java 并发文件写入 - 应该会失败

Java concurrent file writing - should fail

我一直在测试将多个项目写入文件系统,完全期望在一个线程覆盖花药数据或与另一个项目的数据交错时出现故障。

然而下面的代码却意外的通过了。

为什么一个线程的数据没有覆盖另一个线程的数据?所有线程共享一个作者。代码通过是因为 JVM 实现细节,还是真的可以预期不会混淆个别项目。

我看到了一些关于多个线程写入同一个文件的其他任务,但这些都是关于性能优化的。请注意,导入样式只是为了在发布时简洁。

package com.test;

import static org.junit.Assert.assertEquals;

import java.io.*;
import java.nio.charset.*;
import java.nio.file.*;
import java.util.*;

import org.springframework.boot.CommandLineRunner;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.MappingIterator;
import com.fasterxml.jackson.databind.ObjectMapper;

public class DiskWriterApplication implements CommandLineRunner {

    public static void main(String[] args) throws Exception {
        new DiskWriterApplication().run(args);
    }

    @Override
    public void run(String... args) throws Exception {
        Path path = Paths.get(System.getProperty("user.home")+"/java-file.txt");
        if (!Files.exists(path)) {
            Files.createFile(path);
        } else {
            Files.delete(path);
            Files.createFile(path);
        }
        BufferedWriter writer = Files.newBufferedWriter(path, Charset.forName("UTF-8"), StandardOpenOption.APPEND);


        Thread[] threads = new Thread[4];

        for (int i=0; i< 4; i++) {
            threads[i] = new Thread(new DataWriter(writer, createDataItems(i)));
        }

        Arrays.asList(threads).forEach(Thread::start);
        Arrays.asList(threads).forEach(t-> {
            try {
                t.join();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        });
        writer.close();

        //Verify Lines were written correctly
        ObjectMapper mapper = new ObjectMapper();
        MappingIterator<Data> valueIterator = mapper.readerFor(Data.class).readValues(Files.newInputStream(path));

        Set<String> uniqueItems = new HashSet<>();
        int[] groupItemCount = new int[4];
        while (valueIterator.hasNext())
        {
          Data item = valueIterator.next();

          assertEquals("First Item and second Item should be equal", item.firstValue, item.secondValue);
          assertEquals(10, item.innerObject.size());
          assertEquals(20, item.listValues.size());

          for (int i = 0 ; i< 10; i++) {
              assertEquals(item.firstValue, item.innerObject.get("innerProp"+i));
          }
          for (int i = 0 ; i< 20; i++) {
              assertEquals(item.firstValue, item.listValues.get(i));
          }
          uniqueItems.add(item.firstValue);
          groupItemCount[item.group]++;
        }

        System.out.println("Got " + uniqueItems.size() + " uniqueItems");
        assertEquals("Should be 4000 uniqueItems", 4000, uniqueItems.size());
        assertEquals("Should be 1000 items in group[0]", 1000, groupItemCount[0]);
        assertEquals("Should be 1000 items in group[1]", 1000, groupItemCount[1]);
        assertEquals("Should be 1000 items in group[2]", 1000, groupItemCount[2]);
        assertEquals("Should be 1000 items in group[3]", 1000, groupItemCount[3]);
    }



    private List<Data> createDataItems(int groupNumber) {
        List<Data> items = new ArrayList<>();
        for (int i =0; i<1000; i++) {
            Data item = new Data();
            item.group = groupNumber;
            item.itemNumber = i;
            item.firstValue = "{group" + groupNumber + "item" + i + "}";
            item.secondValue = "{group" + groupNumber + "item" + i + "}";
            for (int j =0; j< 10; j ++) {
                item.addInnerProperty("innerProp"+j , "{group" + groupNumber + "item" + i + "}");
            }
            for (int j=0; j<20; j++) {
                item.addListValue("{group" + groupNumber + "item" + i + "}");
            }
            items.add(item);
        }
        return items;
    }


    private class DataWriter implements Runnable {
        private ArrayList<String> data;
        private PrintWriter writer;

        public DataWriter(BufferedWriter writer, List<Data> items) {
            this.writer = new PrintWriter(writer);
            this.data = new ArrayList<String>();

            ObjectMapper mapper = new ObjectMapper();

            for (Data i : items) {
                try {
                    String stringValue = mapper.writeValueAsString(i);
                    data.add(stringValue);
                } catch (JsonProcessingException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }


        @Override
        public void run() {
            System.out.println("Starting batch");
            data.forEach(t -> {
                    writer.println(t);
                    writer.flush();
                    });
            System.out.println("finishing batch");
        }
    }

    public static class Data {
        public int itemNumber;
        public int group;
        @JsonProperty
        private String firstValue;
        @JsonProperty
        private String secondValue;
        @JsonProperty
        private Map<String, String> innerObject = new HashMap<>();
        @JsonProperty
        private List<String> listValues = new ArrayList<>();

        public void addInnerProperty(String key, String value){
            this.innerObject.put(key, value);
        }

        public void addListValue(String value) {
            this.listValues.add(value);
        }

    }
}

正如您在其他线程中看到的,询问同样的事情: Writing a file using multiple threads in java

BufferedWriter 是同步和线程安全的