无法弄清楚异常

时间:2018-02-23 10:54:33

标签: java android multithreading exception concurrency

我正在编写简单的代码到异步将日志写入文件,但发现很难找出一个问题。

我在java.util.NoSuchElementException中获得logNodes.removeFirst()。如果我检查列表是否为空,怎么会发生这种情况?

如果我经常登录,则会出现此问题。

如果有人能向我解释为什么会发生这种情况,我们将非常感激。

我的代码:

private static class FileLogger extends Thread {
    private File logFile;
    private PrintWriter logWriter;
    private final LinkedList<LogNode> logNodes = new LinkedList<>();

    public FileLogger(Context context) {
        String dateString = (String) DateFormat.format("yyyy-MM-dd_HH:mm:ss", new Date());
        File logsDir = new File(context.getCacheDir(), "logs");

        if (logsDir.exists()) {
            for (File file : logsDir.listFiles()) {
                file.delete();
            }
        }

        try {
            logFile = new File(logsDir, dateString + ".log");
            if (!logFile.exists()) {
                logFile.getParentFile().mkdirs();
                logFile.createNewFile();
            }

            logWriter = new PrintWriter(new FileOutputStream(logFile));
            start();
        } catch (IOException ignored) {
        }
    }

    public void log(Date date, String tag, String msg) {
        if (isAlive()) {
            logNodes.addLast(new LogNode(date, tag, msg));
            synchronized (this) {
                this.notify();
            }
        }
    }

    @Override
    public void run() {
        while (true) {
            if (logNodes.isEmpty()) {
                try {
                    synchronized (this) {
                        this.wait();
                    }
                } catch (InterruptedException e) {
                    logWriter.flush();
                    logWriter.close();
                    return;
                }
            } else {
                LogNode node = logNodes.removeFirst();
                SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.US);
                logWriter.println(String.format(
                        "%s %s.%s", dateFormat.format(node.date), node.tag, node.msg
                ));
                logWriter.flush();
            }
        }
    }

    private class LogNode {
        final Date date;
        final String tag;
        final String msg;

        public LogNode(Date date, String tag, String msg) {
            this.date = date;
            this.tag = tag;
            this.msg = msg;
        }
    }
}

1 个答案:

答案 0 :(得分:1)

原因

您没有同步多个log个主题。

假设你有thread1和thread2:

  1. thread1已将node1写入队列。
  2. FileLogger在调用isEmpty时注意到了node1,而thread2则调用了{1}} 没注意到它。
  3. thread2认为这个列表是空的,让列表的第一个和 最后一个节点是node2,这意味着node1已被覆盖。
  4. 由于您没有任何其他同步,FileLogger可能不会注意到node2,因此会引发NoSuchElementException
  5. 解决方案

    请自行实施阻止队列,尝试使用java.util.concurrent提供的BlockigQueue,让它为您执行同步。

    private static class FileLogger extends Thread {
        private File logFile;
        private PrintWriter logWriter;
        private final BlockingQueue<LogNode> logNodes = new LinkedBlockingQueue<>();
    
        public FileLogger(Context context) {
            String dateString = (String) DateFormat.format("yyyy-MM-dd_HH:mm:ss", new Date());
            File logsDir = new File(context.getCacheDir(), "logs");
    
            if (logsDir.exists()) {
                for (File file : logsDir.listFiles()) {
                    file.delete();
                }
            }
    
            try {
                logFile = new File(logsDir, dateString + ".log");
                if (!logFile.exists()) {
                    logFile.getParentFile().mkdirs();
                    logFile.createNewFile();
                }
    
                logWriter = new PrintWriter(new FileOutputStream(logFile));
                start();
            } catch (IOException ignored) {
            }
        }
    
        public void log(Date date, String tag, String msg) {
            if (isAlive()) {
                logNodes.add(new LogNode(date, tag, msg));
            }
        }
    
        @Override
        public void run() {
            while (true) {
                try {
                    LogNode node = logNodes.take();
                    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.US);
                    logWriter.println(String.format(
                            "%s %s.%s", dateFormat.format(node.date), node.tag, node.msg
                    ));
                    logWriter.flush();
                } catch (InterruptedException e) {
                    logWriter.flush();
                    logWriter.close();
                    return;
                }
            }
        }
    }