【问题标题】:Synchronization of access to a file on a per-file basis [duplicate]基于每个文件同步访问文件[重复]
【发布时间】:2015-03-01 01:58:07
【问题描述】:

我正在编写一个基于每个用户记录事件的服务器端程序。我有以下代码可以做到这一点:

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

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

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

【问题讨论】:

  • 或许使用日志框架?
  • 除非绝对必要,否则我宁愿坚持使用标准 Java。
  • Java 有一个标准的日志接口。日志不是您应该尝试重新发明的事情之一,除非您 a) 想学习如何编写日志框架或 b) 有一些非常独特的要求(您似乎没有)。
  • @Hele 不使用日志框架,而这正是您需要的,这会很痛苦。
  • 如果write 抛出异常,你的代码会泄漏资源。

标签: java file concurrency


【解决方案1】:
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();
    }
}

【讨论】:

  • 嗯.. 很有趣。但是同步不是作用于对象本身而不是它们的值吗?
  • 准确地说,Long.valueOf(long x) 为您提供原始 long x 的包装对象。
  • 这样的同步需要Long.valueOf 总是为相同的原始参数提供相同的对象,这是不能保证的。
  • @E_net4 这正是我的问题。
  • synchronized(Long.valueOf(uniqueID).toString().intern()) { BufferedWriter buffWriter = new BufferedWriter(new ileWriter(uniqueID + ".log", true)); buffWriter.write(event); buffWriter.close(); }
【解决方案2】:

我不建议重写日志框架,因为它比听起来要复杂得多,而且几乎可以肯定使用现有的框架会更好。例如,请参阅 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);
    }
  }
}   

【讨论】:

    【解决方案3】:

    我相信同步整个事情的另一种方法是使用异步方法。假设所有日志条目都添加到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");
    

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-09-27
      • 2016-03-04
      • 1970-01-01
      • 1970-01-01
      • 2012-01-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多