9

我编写了一个计算文本中的行数、单词数和字符数的程序:它使用线程来执行此操作。有时效果很好,但有时效果不佳。最终发生的是指向计数的单词和字符数量的变量有时会出现短缺,有时不会。

在我看来,线程有时会在他们计算出他们想要的所有单词或字符之前就结束了。是因为当 while (true) 循环中断时这些线程超出范围吗?

我在下面包含了我的问题的线程部分的代码:

private void countText() {
  try {
    reader = new BufferedReader(new FileReader("this.txt"));
    while (true) {
      final String line = reader.readLine();
      if(line == null) {break;}
      lines++;
      new Thread(new Runnable() {public void run() {chars += characterCounter(line);}}).start();
      new Thread(new Runnable() {public void run() {words += wordCounter(line);}}).start();
      println(line);
    }

  } catch(IOException ex) {return;}

}

(子问题:这是我第一次询问一些事情并发布代码。我不想使用 StackOverflow 代替谷歌和维基百科,并且担心这不是一个合适的问题?我试图让问题更笼统,所以我不只是寻求我的代码帮助......但是,是否有另一个网站可能更适合这类问题?)

4

3 回答 3

7

不同的线程设计将更容易找到和解决此类问题,并且更有效地进行讨价还价。这是一个冗长的回应,但总结是“如果你在 Java 中做线程,请尽快检查java.util.concurrent ”。

我猜你正在多线程处理这段代码来学习线程,而不是加快计算单词的速度,但这是使用线程的一种非常低效的方式。您每行创建两个线程- 一千行文件有两千个线程。创建线程(在现代 JVM 中)使用操作系统资源并且通常相当昂贵。当两个 - 更不用说两千个 - 线程必须访问共享资源(例如您的charswords计数器)时,由此产生的内存争用也会损害性能。

synchronized按照Chris Kimpton 的建议WMR 的建议Atomic制作计数器变量可能会修复代码,但也会使争用的效果更糟。我很确定它会比单线程算法慢。

我建议只有一个长期存在的线程来照顾chars和 一个用于words,每个线程都有一个工作队列,每次您想添加一个新号码时,您都可以向该工作队列提交作业。这样,只有一个线程写入每个变量,如果您对设计进行更改,谁负责什么将更加明显。它也会更快,因为没有内存争用,并且您不会在紧密循环中创建数百个线程。

同样重要的是,一旦您读取了文件中的所有行,在实际打印出计数器的值之前等待所有线程完成,否则您将丢失尚未完成的线程的更新。使用您当前的设计,您必须建立一个您创建的线程的大列表,并在最后运行它以检查它们是否都已死亡。使用队列和工作线程设计,您可以告诉每个线程排空其队列,然后等到它完成。

Java(从 1.5 及更高版本)使这种设计非常容易实现:查看java.util.concurrent.Executors.newSingleThreadExecutor。它还使以后添加更多并发性变得容易(假设适当的锁定等),因为您可以切换到线程池而不是单个线程。

于 2008-11-14T11:49:03.323 回答
4

正如 Chris Kimpton 已经正确指出的那样,您在更新不同线程时遇到了chars问题words。同步this也不起作用,因为this它是对当前线程的引用,这意味着不同的线程将在不同的对象上同步。您可以使用可以同步的额外“锁定对象”,但解决此问题的最简单方法可能是将AtomicIntegers用于 2 个计数器:

AtomicInteger chars = new AtomicInteger();
...
new Thread(new Runnable() {public void run() { chars.addAndGet(characterCounter(line));}}).start();
...

虽然这可能会解决您的问题,但Sam Stoke 更详细的答案是完全正确的,原始设计效率非常低。

要回答有关线程何时“超出范围”的问题:您正在为文件中的每一行启动两个新线程,并且所有线程都将运行,直到它们到达run()方法的末尾。这是除非你让它们成为守护线程),在这种情况下,只要守护线程是唯一仍在此 JVM 中运行的线程,它们就会退出。

于 2008-11-14T11:37:31.303 回答
3

对我来说听起来是个好问题......我认为这个问题可能与字符 += 和单词 += 的原子性有关 - 多个线程可能同时调用它 - 你做任何事情来确保有没有交错。

那是:

线程 1,有 chars = 10,想加 5

线程 2,有 chars = 10,想要添加 3

线程 1 计算出新的总数,15

线程 2 计算出新的总数,13

线程 1 将 chars 设置为 15

线程 2 将 chars 设置为 13。

除非您在更新这些变量时使用同步,否则可能是可能的。

于 2008-11-14T11:17:29.177 回答