多线程应用程序中的可选调试输出——我的意思是*针对客户端*,而不是为了找出死锁或错误

Optional debugging output in a multi-threaded application--I mean *for the client*, not for the sake of figuring out deadlocks or bugs

为了学习,我正在 工作。我真的需要学习它。我已经有一个单线程函数,可以读取目录中的所有文本文件,并将所有缩进制表符替换为三个空格。

它能够传递 Appendable 以获得可选的额外信息(列出每个文件、给出统计信息等)。如果他们传入 null,他们不需要调试。

我正在尝试确定在多线程版本中处理此问题的最佳方式是什么,但搜索 "debugging multi-threaded java" 除了如何诊断错误和死锁外,我一无所获。

我可以安全地坚持使用 Appendable 还是应该考虑其他事情?我不确定如何处理交错消息,但我想弄清楚的第一件事是线程安全。

与其传入 Appendable,不如考虑在您的库中使用 slf4j 来进行日志记录。

如果在 运行 时没有链接日志框架,则不会进行任何日志记录。如果应用程序已经在进行日志记录,那么 slf4j 可能会输出到它的前端。

我建议使用 Logback 作为日志输出,因为它可以通过配置文件或直接在代码中很好地配置。要获得基本输出,您需要做的就是包含 JAR。

从多线程使用 Appendable 的问题是它没有被指定为线程安全的。

Thread safety is the responsibility of classes that extend and implement this interface.

因此,答案是使用线程安全的多路复用器。这个使用 BlockingQueue 和一个从中提取数据并将其转发到他们的 Appendable.

的线程
class TellThemWhatIsHappening implements Appendable {

    // The pipe to their system/log.
    private final Appendable them;
    // My internal queue.
    private final BlockingQueue<String> queue = new ArrayBlockingQueue<>(10);
    // Hav I been interrupted?
    private volatile boolean interrupted = false;

    public TellThemWhatIsHappening(Appendable them) {
        // Record the target Appendable.
        this.them = them;
        // Grow my thread.
        Thread t = new Thread(consumer);
        // Make sure it doesn't hold your app open.
        t.setDaemon(true);
        // Start the consumer runnning.
        t.start();
    }

    // The runnable that consumes the queue and passes it on to them.
    private Runnable consumer = new Runnable() {

        @Override
        public void run() {
            while (!interrupted) {
                try {
                    // Pull from the queue and push to them.
                    them.append(queue.take());
                } catch (InterruptedException ex) {
                    // We got interrupted.
                    interrupted = true;
                } catch (IOException ex) {
                    // Not sure what you shoudl do here. Their appendable threw youy an exception.
                    interrupted = true;
                }
            }
        }

    };

继续...

    private void append(String s) throws IOException {
        // No point if they are null.
        if (them != null) {
            try {
                queue.put(s);
            } catch (InterruptedException ex) {
                // What should we do here?
                interrupted = true;
            }
        }
    }

    @Override
    public Appendable append(CharSequence csq) throws IOException {
        append(csq.toString());
        return this;
    }

    @Override
    public Appendable append(CharSequence csq, int start, int end) throws IOException {
        append(csq.subSequence(start, end).toString());
        return this;
    }

    @Override
    public Appendable append(char c) throws IOException {
        append("" + c);
        return this;
    }

}

但是 - 使用适当的日志记录系统而不是开发自己的日志记录系统是一个非常好的主意。

调试线程通常是试图弄清楚表示的情况。 Log4j 通常很棒。您可以将其配置为使用线程名称和时间戳标记每一行。执行此操作后,您可以根据线程名称过滤输出并关注单个线程。

一个好的过滤工具真的很重要。最基本的是通过 grep 对其进行尾部处理和管道传输——但如果这是你经常做的事情,你可能想在日志之上添加一些东西——比如一个带有每个线程选项卡的 GUI 或类似的东西。

Log4j本身处理线程不会有问题。

如果你真的想自己做,给每个线程传入一个不同的可追加文件,然后当线程完成后转储它或将它保存到一个文件中。您可能只想使用一个线程来 dump/save 附加项。