为什么我的线程没有醒来? (JAVA)

我正在松散地关注Java NIO的教程,以创建我的第一个multithreading,网络Java应用程序。 本教程基本上是关于创建一个echo-server和一个客户端,但目前我只是试图获取服务器从客户端接收消息并将它们记录到控制台。 通过在教程页面中搜索“EchoServer”,您可以看到我基于大多数相关代码的类。

我的问题是(至少我认为是)我找不到初始化要处理的消息队列的方法,以便可以按我的意愿使用它。

应用程序在两个线程上运行:服务器线程,用于侦听连接和套接字数据;以及工作线程,用于处理服务器线程接收的数据。 当服务器线程收到消息时,它会调用worker上的processData(byte[] data) ,并将数据添加到队列中:

 1. public void processData(byte[] data) { 2. synchronized(queue) { 3. queue.add(new String(data)); 4. queue.notify(); 5. } 6. } 

在worker thread的run()方法中,我有以下代码:

 7. while (true) { 8. String msg; 9. 10. synchronized (queue) { 11. while (queue.isEmpty()) { 12. try { 13. queue.wait(); 14. } catch (InterruptedException e) { } 15. } 16. msg = queue.poll(); 17. } 18. 19. System.out.println("Processed message: " + msg); 20. } 

我已经在调试器中validation了工作线程到达第13行,但是在服务器启动时没有进入第16行。 我认为这是成功等待的标志。 我还validation了服务器线程到达第4行,并在队列上调用notify() 。 但是, 工作线程似乎没有醒来

wait()的javadoc中,声明了这一点

当前线程必须拥有此对象的监视器。

鉴于我对线程缺乏经验,我不确定这意味着什么,但我已经尝试从工作线程实例化队列但没有成功。

为什么我的线程没有醒来? 我该如何正确唤醒它?


更新:

正如@Fly建议的那样,我添加了一些日志调用来打印出System.identityHashCode(queue) ,确定队列是不同的实例。

这是整个Worker类:

 public class Worker implements Runnable { Queue queue = new LinkedList(); public void processData(byte[] data) { ... } @Override public void run() { ... } } 

worker在main方法中实例化,并按如下方式传递给服务器:

 public static void main(String[] args) { Worker w = new Worker(); // Give names to threads for debugging purposes new Thread(w,"WorkerThread").start(); new Thread(new Server(w), "ServerThread").start(); } 

服务器将Worker实例保存到私有字段,并在该字段上调用processData() 。 为什么我不能获得相同的队列?


更新2:

现在可以在此处获得服务器和工作线程的完整代码。

我已将两个文件中的代码放在同一个粘贴中,因此如果您想自己编译并运行代码,则必须再次将它们拆分。 此外,还有对Log.d()Log.i()Log.w()Log.e() – 这些只是简单的日志记录例程,它们构造带有一些额外信息的日志消息(时间戳等) )并输出到System.outSystem.err

我猜你会得到两个不同的queue对象,因为你正在创建一个全新的Worker实例。 你没有发布启动Worker的代码,但假设它也实例化并启动了Server ,那么问题出在你分配this.worker = new Worker(); 而不是将其分配给Worker参数。

  public Server(Worker worker) { this.clients = new ArrayList(); this.worker = new Worker(); // <------THIS SHOULD BE this.worker = worker; try { this.start(); } catch (IOException e) { Log.e("An error occurred when trying to start the server.", e, this.getClass()); } } 

Worker的线程可能正在使用传递给Server构造函数的worker实例,因此Server需要将自己的worker引用分配给同一个Worker对象。

您可能希望使用LinkedBlockingQueue ,它在内部处理multithreading部分,您可以更专注于逻辑。 例如 :

 // a shared instance somewhere in your code LinkedBlockingQueue queue = new LinkedBlockingQueue(); 

在你的一个线程中

 public void processData(byte[] data) { queue.offer(new String(data)); } 

并在你的其他线程

 while (running) { // private class member, set to false to exit loop String msg = queue.poll(500, TimeUnit.MILLISECONDS); if (msg == null) { // queue was empty Thread.yield(); } else { System.out.println("Processed message: " + msg); } } 

注意:为了完整起见,方法poll会抛出您可以根据需要处理的InterruptedException 。 在这种情况下, while可以被try...catch包围,所以如果线程应该被中断则退出。

我假设该queue是某个实现Queue接口的类的实例,并且(因此) poll()方法不会阻塞。

在这种情况下,您只需要实例化一个可由两个线程共享的队列对象。 以下将做到这一点:

 Queue queue = new LinkedList(); 

LinkedList类不是线程安全的,但前提是您始终在synchronized(queue)块中访问和更新队列实例,这将处理线程安全。

我认为其余代码是正确的。 您似乎正在正确执行等待/通知。 工作线程应该获取并打印消息。

如果这不起作用,那么首先要检查的是两个线程是否使用相同的队列对象。 要检查的第二件事是是否实际调用了processData 。 第三种可能性是其他一些代码正在添加或删除队列条目,并以错误的方式执行。

如果调用notify()时没有线程hibernate,则notify()调用将丢失。 所以,如果你去notify()然后另一个线程确实wait() ,那么你将死锁。

您想要使用信号量。 与条件变量不同, release()/increment()调用不会在信号量上丢失。

将信号量计数设置为零。 当您添加到队列时增加它。 从队列中取出时减少它。 这样你就不会丢失叫醒电话。

更新

澄清关于条件变量和信号量的一些混淆。

条件变量和信号量之间存在两个差异。

  1. 与信号量不同,条件变量与锁相关联。 您必须在调用wait()notify()之前获取锁。 信号量没有这个限制。 此外, wait()调用释放锁定。
  2. notify()调用在条件变量上丢失,这意味着,如果调用notify()并且没有线程因调用wait()而hibernate,则notify()将丢失。 信号量不是这种情况。 对信号量的acquire()release()调用的顺序无关紧要,因为信号量保持计数。 这就是为什么它们有时被称为计数信号量。

在wait()的javadoc中,声明了这一点

 The current thread must own this object's monitor. 

鉴于我对线程缺乏经验,我不确定这意味着什么,但我已经尝试从工作线程实例化队列但没有成功。

他们使用非常奇怪和令人困惑的术语。 作为一般的经验法则,Java中的“对象监视器”意味着“对象的锁定”。 Java中的每个对象都有一个锁和一个条件变量( wait() / notify() )。 那么该行意味着,在对对象(在你的情况下是队列对象)上调用wait()notify() ,你很多都会获得带有synchronized(object){}的锁。 在Java中使用监视器“内部”意味着拥有使用synchronized()的锁。 该术语已经从研究论文中采用并应用于Java概念,因此它有点令人困惑,因为这些词语的含义与它们原来的含义略有不同。

代码似乎是正确的。

两个线程都使用相同的队列对象吗? 您可以在调试器中通过对象ID进行检查。

将notify()更改为notifyAll()有帮助吗? 可能有另一个线程在队列上调用wait()。

好吧,经过几个小时毫无意义地环顾网络之后,我决定只是把代码搞砸了一会儿,看看我能得到什么。 这工作:

 private static BlockingQueue queue; private BlockingQueue getQueue() { if (queue == null) { queue = new LinkedBlockingQueue(); } return queue; } 

正如Yanick Rochon指出的那样 ,通过使用BlockingQueue而不是普通的Queue可以略微简化代码,但是产生差异的变化是我实现了Singleton模式。

由于这解决了我使应用程序正常工作的直接问题,我会称之为答案。 大量的荣誉应该归@Fly和其他人指出Queue实例可能不一样 – 如果没有我永远不会想到这一点。 但是,我仍然非常好奇为什么我必须这样做,所以我马上就会提出一个新的问题。