我正在松散地跟随a tutorial on Java NIO创建我的第一个多线程网络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. }
在工作线程的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. }
我已经在调试器中验证了工作线程到达第13行,但是在服务器启动时没有进入第16行。我认为这是成功等待的标志。我还验证了服务器线程到达第4行,并在队列上调用notify()
。但是,工作线程似乎没有醒来。
在wait()
的javadoc中,声明
当前线程必须拥有此对象的监视器。
鉴于我对线程的经验不足,我不确定这意味着什么,但我尝试从工作线程中实例化队列但没有成功。
为什么我的线程没有醒来?我该如何正确唤醒它?
正如@Fly建议的那样,我添加了一些日志调用来打印出System.identityHashCode(queue)
,并确定队列是不同的实例。
这是整个Worker
类:
public class Worker implements Runnable {
Queue<String> queue = new LinkedList<String>();
public void processData(byte[] data) { ... }
@Override
public void run() { ... }
}
工作程序在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()
。为什么我没有得到相同的队列?
服务器和工作线程的整个代码现在可用here。
我已将两个文件中的代码放在同一个粘贴中,因此如果您想自己编译并运行代码,则必须重新拆分它们。此外,对Log.d()
,Log.i()
,Log.w()
和Log.e()
的调用也很少 - 这些只是简单的日志记录例程,它们构造带有一些额外信息的日志消息(时间戳等) )并输出到System.out
和System.err
。
答案 0 :(得分:2)
您可能希望使用LinkedBlockingQueue
,它在内部处理多线程部分,您可以更专注于逻辑。例如:
// a shared instance somewhere in your code
LinkedBlockingQueue<String> queue = new LinkedBlockingQueue<String>();
在你的一个帖子中
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
包围,因此如果线程应该被中断,则退出。
答案 1 :(得分:2)
我猜你会得到两个不同的queue
个对象,因为你正在创建一个全新的Worker
实例。您没有发布启动Worker
的代码,但假设它还实例化并启动了Server
,那么问题出在您指定this.worker = new Worker();
而不是分配它的行上到Worker
参数。
public Server(Worker worker) {
this.clients = new ArrayList<ClientHandle>();
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对象。
答案 2 :(得分:1)
我假设queue
是实现Queue
接口的某个类的实例,并且(因此)poll()
方法不会阻塞。
在这种情况下,您只需要实例化可由两个线程共享的单个队列对象。以下将解决这个问题:
Queue<String> queue = new LinkedList<String>();
LinkedList
类不是线程安全的,但假设您始终在synchronized(queue)
块中访问和更新队列实例,这将处理线程安全。
我认为其余代码是正确的。您似乎正在正确执行等待/通知。工作者线程应该获取并打印消息。
如果这不起作用,那么首先要检查的是两个线程是否使用相同的队列对象。要检查的第二件事是processData
是否实际被调用。第三种可能性是其他一些代码正在添加或删除队列条目,并以错误的方式执行。
答案 3 :(得分:0)
notify()
时没有线程处于休眠状态,则会失去 notify()
个调用。所以,如果你去notify()
,那么另一个线程之后会wait()
,那么你就会陷入僵局。
您想要使用信号量。与条件变量不同,release()/increment()
调用不会在信号量上丢失。
将信号量计数设为零。当您添加到队列时增加它。从队列中取出时减少它。你不会以这种方式丢失叫醒电话。
<强>更新强>
澄清关于条件变量和信号量的一些混淆。
条件变量和信号量之间存在两个差异。
wait()
和notify()
之前,您必须获得锁定。信号量没有这个限制。此外,wait()
调用会释放锁定。notify()
调用在条件变量上丢失,这意味着,如果您调用notify()并且没有线程因调用wait()
而休眠,则notify()
将丢失。信号量不是这种情况。 acquire()
和release()
调用信号量的顺序无关紧要,因为信号量保持计数。这就是他们有时被称为计数信号量的原因。答案 4 :(得分:0)
在wait()的javadoc中,声明
The current thread must own this object's monitor.
鉴于我对线程缺乏经验,我不确定那是什么 意思是,但我已经尝试从工作线程实例化队列 没有成功。
他们使用非常奇怪和令人困惑的术语。作为一般的经验法则,Java中的“对象监视器”意味着“对象的锁定”。 Java中的每个对象都有一个锁和一个条件变量(wait()
/ notify()
)。那么该行的意思是,在你对一个对象(在你的情况下是队列对象)上调用wait()
或notify()
之前,你会用synchronized(object){}
拳头获得锁定。在Java中使用监视器“内部”意味着拥有synchronized()
的锁。该术语已经从研究论文中采用并应用于Java概念,因此有点令人困惑,因为这些词语的含义与它们原来的含义略有不同。
答案 5 :(得分:0)
代码似乎是正确的。
两个线程都使用相同的队列对象吗?您可以在调试器中通过对象ID进行检查。
将notify()更改为notifyAll()有帮助吗?可能有另一个线程在队列上调用wait()。
答案 6 :(得分:0)
好的,经过几个小时毫无意义地环顾网络之后,我决定将代码搞砸了一会儿,看看我能找到什么。这很有效:
private static BlockingQueue<String> queue;
private BlockingQueue<String> getQueue() {
if (queue == null) {
queue = new LinkedBlockingQueue<String>();
}
return queue;
}
作为Yanick Rochon pointed out,使用BlockingQueue
而不是普通的Queue
可以略微简化代码,但是产生差异的变化是我实现了Singleton模式。
由于这解决了我使应用程序正常工作的直接问题,我会称之为答案。应该向@Fly和其他人发出大量的赞誉,指出Queue
实例可能不一样 - 如果没有我永远不会想到这一点。但是,我仍然非常好奇为什么我必须这样做,所以我马上会问一个新的问题。