基于每个文件同步对文件的访问

时间:2015-01-02 19:03:08

标签: java file concurrency

我正在编写一个服务器端程序,以每个用户为基础记录事件。我有以下代码来执行此操作:

public void logUser(long uniqueID, String event) throws IOException
{
    BufferedWriter buffWriter = new BufferedWriter(new FileWriter(uniqueID + ".log", true));
    buffWriter.write(event);
    buffWriter.close();
}

现在,由于时间差很小,可能会在同一个用户的不同线程上调用多个不同的事件,因此我希望基于每个用户同步此过程。这是因为使用的文件对每个用户都是唯一的。

我想知道实现这一目标的最有效方法。

3 个答案:

答案 0 :(得分:4)

我不建议重写日志框架,因为它比听起来要复杂得多,你几乎肯定会更好地使用现有框架。有关各种备选方案,请参阅Java Logging vs Log4JWhy not use java.util.logging?

但是对于您的用例,您可以使用ConcurrentHashMap个锁:

ConcurrentMap<Long, Object> locks = new ConcurrentHashMap<> ();

public void logUser(long uniqueID, String event) throws IOException {
  Object lock = locks.computeIfAbsent(uniqueID, i -> new Object());
  synchronized(lock) {
    try (BufferedWriter buffWriter =
                 new BufferedWriter(new FileWriter(uniqueID + ".log", true));) {
      buffWriter.write(event);
    }
  }
}   

答案 1 :(得分:2)

我认为同步整个事情的另一种方法是使用异步方法。假设所有日志条目都添加到BlockingQueue,而其他一些线程则使用该队列。然后,不需要同步。

示例:

public class LogAsync {
    // Some kind of abstraction for a log entry
    public static class LogEntry {
        private final String event;
        private final long uniqueId;

        public LogEntry(long uniqueId, String event) {
            this.uniqueId = uniqueId;
            this.event = event;
        }

        public String getEvent() {
            return event;
        }

        public long getUniqueId() {
            return uniqueId;
        }
    }

    // A blocking queue where the entries are stored    
    private final BlockingQueue<LogEntry> logEvents = new LinkedBlockingQueue<>();

    // Adds a log entry to the blocking queue    
    public void logUser(long uniqueID, String event) {
        logEvents.add(new LogEntry(uniqueID, event));
    }

    // Starts the thread that handles the "writing to file"
    public LogAsync start() {
        // Run in another thread
        CompletableFuture.runAsync(() -> {
                    while (true) {
                        try {
                            final LogEntry entry = logEvents.take();

                            try (BufferedWriter buffWriter = new BufferedWriter(new FileWriter(entry.getUniqueId() + ".log", true))) {
                                buffWriter.write(entry.getEvent());
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        } catch (InterruptedException e) {
                            break;
                        }
                    }
                }
        );
        return this;
    }
}

初始化整个事物的方式可能是这样的:

final LogAsync logger = new LogAsync().start();
logger.logUser(1, "Hello");
logger.logUser(1, "there");
logger.logUser(2, "Goodbye");

所以,这基本上是同步整个事物的替代方案。请注意,此代码仅为示例代码,并且必须进行一些修改才能使生产有价值。例如,必须有一种很好的方法来关闭writer线程。

但是,我的建议是使用同步或异步方法。相反,使用日志框架,例如SLF4J

答案 2 :(得分:0)

public void logUser(long uniqueID, String event) throws IOException
{
    synchronized(Long.valueOf(uniqueID)) {
        BufferedWriter buffWriter = new BufferedWriter(new FileWriter(uniqueID + ".log", true));
        buffWriter.write(event);
        buffWriter.close();
    }
}