【问题标题】:How to do thread interacting?如何进行线程交互?
【发布时间】:2016-06-09 18:41:16
【问题描述】:

我想使用多线程(低级线程),但我遇到了问题。问题是因为至少等待方法将被一个线程调用并且 notifyAll 将被另一个线程调用问题是任何时候我运行程序在我看来 notifyAll 在等待之前被调用所以我“将永远等待”。

我的代码如下:

public class Reader extends Thread {

    Calculator c;

    public Reader(Calculator cal) {
        c=cal;
    }

    public void run (){
        synchronized(c) {
            try {
                System.out.println("Waiting for calculation");
                c.wait();
            } catch (InterruptedException ex) {}
            System.out.println("Total is:" +c.total);      
        }       
    }  

    public static void main (String[] a) {

        Calculator calculator=new Calculator();

        new Reader(calculator).start();
        new Reader(calculator).start();
        new Reader(calculator).start();
        new Reader(calculator).start();
        new Reader(calculator).start();
    }
}

class Calculator implements Runnable {

    int total;

    public void run()  {
        synchronized(this) {    
            for(int i=0;i<100;i++) {
                total=total+i;
            }       
            notifyAll();    
        }
    }
}

我在这里得到的输出是连续 5 次等待计算,所以我永远不会达到“总数就是总数”的语句。

我正在尝试找出解决问题的方法,但仍未找到解决方案。如果有人知道该怎么做,我将不胜感激。

提前致谢

【问题讨论】:

    标签: java multithreading wait notify


    【解决方案1】:

    您实际上并没有执行 Calculator 可运行文件,您拥有的代码不会计算总数或通知任何内容。使用计算器启动一个新线程:

    new Thread(calculator).start();
    

    在初始化计算器局部变量的那一行之后。这将使计算器在主线程启动读取器时开始工作。

    计算器可能会在读者开始等待之前完成,而读者最终将永远等待永远不会到来的通知。创建一个条件变量,在本例中为计算器添加一个布尔标志,在计算完成后将其初始化为 false 并设置为 true(通过 notifyAll,在离开同步块之前)。让读者循环等待:

    synchronized(c) {
        try {
            System.out.println("Waiting for calculation");
            while (!c.finished) {
                c.wait();
            }
        } catch (InterruptedException ex) {}
        System.out.println("Total is:" +c.total);
    }
    

    这样,万一计算器在读者开始之前就结束了,读者会从条件变量中得知计算器已经结束,他们不会等待。

    最好总是在循环中调用 wait 方法,因为

    a) 当线程收到通知时它没有锁,在线程被通知到它重新获得锁的时间之间,系统的状态可能会由于其他线程的操作而改变(不是本例中的情况),以及

    b) 你不能依赖等待的终止,这意味着线程收到了通知。

    通过上述更改,程序正常结束,输出如下:

    Waiting for calculation
    Total is:4950
    Waiting for calculation
    Total is:4950
    Waiting for calculation
    Total is:4950
    Waiting for calculation
    Total is:4950
    Waiting for calculation
    Total is:4950
    

    这是修改后的完整代码:

    public class Reader extends Thread {
    
        Calculator c;
    
        public Reader(Calculator cal) {
            c=cal;
        }
    
        public void run (){
            synchronized(c) {
                try {
                    System.out.println("Waiting for calculation");
                    while (!c.finished) {
                        c.wait();
                    }
                } catch (InterruptedException ex) {}
                System.out.println("Total is:" +c.total);      
            }       
        }  
    
        public static void main (String[] a) {
    
            Calculator calculator=new Calculator();
            new Thread(calculator).start();
    
            new Reader(calculator).start();
            new Reader(calculator).start();
            new Reader(calculator).start();
            new Reader(calculator).start();
            new Reader(calculator).start();
        }
    }
    
    class Calculator implements Runnable {
    
        int total;
        boolean finished;
    
        public void run()  {
            synchronized(this) {    
                finished = false;
                for(int i=0;i<100;i++) {
                    total=total+i;
                }       
                notifyAll();    
                finished = true;
            }
        }
    } 
    

    【讨论】:

    • 我只有一个问题。我知道每个执行线程都是从 Thread 类的一个实例开始的。因此,当我扩展线程类时,就像我对阅读器类所做的那样,以便实例化一个线程(因此从新状态传递到线程对象的可运行状态),我们会这样做:new Reader.start() 。所以现在我想知道为什么当我简单地执行 new Reader(calculator).start(); 时我的代码没有为计算器类实例化一个新的线程实例;看起来它必须工作,但它显然工作不正常。你能告诉我吗
    • @ENIO:您没有为计算器创建线程。我不清楚你对什么感到困惑。如果您将 Runnable 作为构造函数 arg 传递给新线程,则该线程将执行该 Runnable。但是 Reader 会覆盖 Thread 的 run 方法,不会导致运行计算器。最好完全避免子类化 Thread,它只会引起混乱。
    • 所有的颠簸是怎么回事?
    【解决方案2】:

    我建议您澄清一下您对最终结果的期望。貌似500?不确定:P

    无论如何,我推荐使用 Java Executors。它们是以有组织的方式处理执行线程的默认 Java 类。试试这个:

    public class Example {
        public static class Calculator implements Runnable {
            private int result = 0;
    
            public void run() {
                for (int i = 0; i < 100; i++) {
                    result++;
                }
    
                System.out.println("Done. Result = " + result);
            }
    
            public int getResult() {
                return result;
            }
        }
    
        public static void main(final String[] args) {
            final Calculator c = new Calculator();
            final ExecutorService executorService = Executors.newSingleThreadExecutor();
            executorService.submit(c);
            executorService.submit(c);
            executorService.submit(c);
            executorService.submit(c);
            executorService.submit(c);
        }
    }
    

    这给了我以下信息:

    Done. Result = 100
    Done. Result = 200
    Done. Result = 300
    Done. Result = 400
    Done. Result = 500
    

    这段代码的作用是创建一个单线程执行器,它可以运行任务(Runnable 对象)并为此使用单线程。因为它使用单个线程,所以每个任务都必须等待前一个线程才能启动。

    如果你创建一个多线程执行器,它会尝试在尽可能多的可用线程中运行任务,所有这些都将遵循与单线程相同的原则:对于每个线程,任务必须等待它之前的一个开始。但是对于多线程执行器,顾名思义,有多个线程,因此这意味着任务将同时运行,并且您现在的行为将显示出来。

    希望这会有所帮助!

    【讨论】:

      【解决方案3】:

      尽管您写了关于低级线程的文章,但很明显您的问题可以通过ForkJoinPool 解决。关键是使用wait - notify 调用很容易出错;但是“发生在之前”的关系可以在不调用wait 的情况下设置,而是使用join 方法。

      完整代码:

      import java.util.concurrent.Callable;
      import java.util.concurrent.ExecutionException;
      import java.util.concurrent.ForkJoinPool;
      import java.util.concurrent.ForkJoinTask;
      import java.util.concurrent.Future;
      import java.util.logging.Level;
      import java.util.logging.Logger;
      
      public class Reader extends Thread {
      
          final Future<Integer> future;
      
          public Reader(Future<Integer> future) {
              this.future = future;
          }
      
          @Override
          public void run() {
              try {
                  int result = future.get();
                  System.out.println("Total is:" + result);
              } catch (InterruptedException | ExecutionException ex) {
                  Logger.getLogger(Reader.class.getName()).log(Level.SEVERE, null, ex);
              }
          }
      
          public static void main(String[] a) {
              Calculator calculator = new Calculator();
              ForkJoinPool pool = ForkJoinPool.commonPool();
              ForkJoinTask calcTask = pool.submit(calculator);
              ForkJoinTask[] tasks = {
                  calcTask,
                  pool.submit(new Reader(calcTask)),
                  pool.submit(new Reader(calcTask)),
                  pool.submit(new Reader(calcTask)),
                  pool.submit(new Reader(calcTask)),
                  pool.submit(new Reader(calcTask))
              };
              //do something else
              for (ForkJoinTask task : tasks) {
                  task.join();
              }
          }
      }
      
      class Calculator implements Callable<Integer> {
      
          @Override
          public Integer call() throws Exception {
              int total = 0;
              for (int i = 0; i < 100; i++) {
                  total = total + i;
              }
              return total;
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-04-27
        • 2015-07-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多