【问题标题】:Multithreaded printing from ArrayList来自 ArrayList 的多线程打印
【发布时间】:2019-06-07 19:49:06
【问题描述】:

我正在尝试让记录器将所有日志消息从应用程序打印到控制台,并在将来打印一个外部文件。当我触发函数时它应该这样做:“dumpToConsole”。它应该通过 3 个线程完成多线程,所有 3 个线程都访问 CopyOnWriteArrayList。问题是输出不正常,是应有的三倍。我得到的不是 3 条消息,而是 9。例如,我需要 3 个线程来打印 1,而不是每个线程打印 3。

下面是我的实际实现。

我的话题:

public class LoggingThread extends Thread {
private boolean isStopped = false;
private CopyOnWriteArrayList<LogMessage> messages;

public LoggingThread(CopyOnWriteArrayList messages) {
    this.messages = messages;
}

@Override
public void run() {
    for (int i = 0; i < messages.size(); i++) {
        writeMessageToConsole(messages.get(i).getMessageText(), messages.get(i).getLogLevel());
    }
}

private synchronized void writeMessageToConsole(String message, LogLevel logLevel) {
    System.out.println(message + " (" + logLevel + ")");
}
}

我的记录器:

public class Logger implements ILogger {
private static ILogger instance = null;
private CopyOnWriteArrayList<LogMessage> messages = new CopyOnWriteArrayList<LogMessage>();
private LoggingThread thread1;
private LoggingThread thread2;
private LoggingThread thread3;

public static ILogger getInstance() {
    if (instance == null) {
        instance = new Logger();
    }

    return instance;
}

public CopyOnWriteArrayList<LogMessage> getMessages() {
    return messages;
}

public void log(Exception ex) {
    log(ex.getMessage(), LogLevel.FATAL);
}

public void log(String message, LogLevel logLevel) {
    messages.add(new LogMessage(message, logLevel));
}

public LogMessage getLastLog() {
    if(!messages.isEmpty()) {
        return messages.get(messages.size() -1);
    }

    else {
        return new LogMessage("", LogLevel.DEBUG);
    }
}

public void dumpToConsole() {
    log("TEST1", LogLevel.FATAL);
    log("TEST2", LogLevel.DEBUG);
    log("TEST3", LogLevel.FATAL);

    thread1 = new LoggingThread(this.messages);
    thread2 = new LoggingThread(this.messages);
    thread3 = new LoggingThread(this.messages);

    thread1.start();
    thread2.start();
    thread3.start();

    try {
        thread1.join();
        thread2.join();
        thread3.join();
    }

    catch (InterruptedException e) {
        log(e.getMessage(), LogLevel.FATAL);
    }

    thread1.interrupt();
    thread2.interrupt();
    thread3.interrupt();
}
}

还有我的消息类:

public class LogMessage {
private String messageText;
private LogLevel logLevel;

public LogMessage(String messageText, LogLevel logLevel) {
    this.messageText = messageText;
    this.logLevel = logLevel;
}

public LogLevel getLogLevel() {
    return logLevel;
}

public String getMessageText() {
    return messageText;
}
}

结果是:

TEST1 (FATAL)
TEST2 (DEBUG)
TEST3 (FATAL)
TEST1 (FATAL)
TEST1 (FATAL)
TEST2 (DEBUG)
TEST3 (FATAL)
TEST2 (DEBUG)
TEST3 (FATAL)

【问题讨论】:

  • 为什么你有超过 1 个记录器线程??

标签: java multithreading logging thread-safety threadpool


【解决方案1】:

一个明显的非答案:忘记在这里使用 3 个线程。

问题是输出不按顺序排列,是应有的输出的三倍。我收到 9 条消息,而不是 3 条消息。

当然。因为你让三个 3 线程做一个线程应该做的工作,而每个线程又做同样的工作。

所以,首先:一旦你说“多线程”,并且你谈论打印列表内容,那么所有关于排序的赌注都被取消了。当然,每个线程都会以正确的顺序打印消息,但是您没有控制 T1 先打印所有消息,还是只打印一条,然后从 T2 打印 3 条,等等。 “未同步”线程的本质是:未定义的顺序。

然后:即使您添加了必要的步骤以以某种方式“同步”您的线程,您也不会从中获得什么

内存中有一个列表。有一个输出文件(或控制台)。使用多个线程来获取值并将它们放入您的文件不会加速任何事情!它只会产生开销,正如所见:它需要大量“同步”代码。

【讨论】:

    【解决方案2】:

    每个线程应该运行不同的任务。 您实现了 run(),因此线程将遍历所有消息,而不是让每个线程大量消息。

    给每个线程它应该写入控制台的索引。 假设您要创建 3 个线程并且您有 12 条消息,那么: 线程 1 将打印 0 到 3, 线程 2 将打印 4 到 7, 线程 3 将打印 8 到 11

    public class LoggingThread extends Thread {
    private boolean isStopped = false;
    private CopyOnWriteArrayList<LogMessage> messages;
    private int start,end;
    
    public LoggingThread(CopyOnWriteArrayList messages, int start, int end) {
        this.messages = messages;
        this.start=start;
        this.end=end;
    }
    
    @Override
    public void run() {
        for (int i = start; i < messages.size() && i<end; i++) {
            writeMessageToConsole(messages.get(i).getMessageText(), messages.get(i).getLogLevel());
        }
    }
    
    private synchronized void writeMessageToConsole(String message, LogLevel logLevel) {
        System.out.println(message + " (" + logLevel + ")");
    }
    }
    

    并创建您的日志:

    thread1 = new LoggingThread(this.messages, 0, this.messages.length()/3);
    thread2 = new LoggingThread(this.messages, this.messages/3, 2*(this.messages.length()/3));
    thread3 = new LoggingThread(this.messages, 2*(this.messages.length()/3), this.messages.length());
    

    每个线程都有三分之一的消息要打印。

    关于顺序...嗯,关于线程的问题是它们同时运行,所以你无法真正知道哪个会先完成以及何时打印每个日志。

    但是,您可以等待每个线程完成 - 在开始下一个线程之前。这将使日志保持有序:

       thread1.start();
       thread1.join();
       thread2.start();
       thread2.join();
       thread3.start();
       thread3.join();
    

    但是那么使用三个线程就没有意义了。

    【讨论】:

    • 您的最后一个建议在某种程度上违背了线程的目的。那时调用run() 3 次将是等效的。
    • @BoristheSpider Yap.. 这就是为什么我在求幂之后写它的原因,它并不是线程的真正用途。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-10-09
    相关资源
    最近更新 更多