简体   繁体   中英

Why does my thread not wake up? (Java)

I'm loosely following a tutorial on Java NIO to create my first multi-threading, networking Java application. The tutorial is basically about creating an echo-server and a client, but at the moment I'm just trying to get as far as a server receiving messages from the clients and logging them to the console. By searching the tutorial page for "EchoServer" you can see the class that I base most of the relevant code on.

My problem is (at least I think it is) that I can't find a way to initialize the queue of messages to be processed so that it can be used as I want to.

The application is running on two threads: a server thread, which listens for connections and socket data, and a worker thread which processes data received by the server thread. When the server thread has received a message, it calls processData(byte[] data) on the worker, where the data is added to a queue:

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

In the worker thread's run() method, I have the following code:

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. }

I have verified in the debugger that the worker thread gets to line 13, but doesn't proceed to line 16, when the server starts. I take that as a sign of a successful wait. I have also verified that the server thread gets to line 4, and calls notify() on the queue. However, the worker thread doesn't seem to wake up .

In the javadoc for wait() , it is stated that

The current thread must own this object's monitor.

Given my inexperience with threads I am not exactly certain what that means, but I have tried instantiating the queue from the worker thread with no success.

Why does my thread not wake up? How do I wake it up correctly?


Update:

As @Fly suggested, I added some log calls to print out System.identityHashCode(queue) and sure enough the queues were different instances.

This is the entire Worker class:

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

The worker is instantiated in the main method and passed to the server as follows:

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();
}

The server saves the Worker instance to a private field and calls processData() on that field. Why do I not get the same queue?


Update 2:

The entire code for the server and worker threads is now available here .

I've placed the code from both files in the same paste, so if you want to compile and run the code yourself, you'll have to split them up again. Also, there's abunch of calls to Log.d() , Log.i() , Log.w() and Log.e() - those are just simple logging routines that construct a log message with some extra information (timestamp and such) and outputs to System.out and System.err .

You might want to use LinkedBlockingQueue instead, it internally handles the multithreading part, and you can focus more on logic. For example :

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

in one of your thread

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

and in your other thread

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);
   }
}

Note : for the sake of completeness, the methode poll throws in InterruptedException that you may handle as you see fit. In this case, the while could be surrounded by the try...catch so to exit if the thread should have been interrupted.

I'm going to guess that you are getting two different queue objects, because you are creating a whole new Worker instances. You didn't post the code that starts the Worker , but assuming that it also instantiates and starts the Server , then the problem is on the line where you assign this.worker = new Worker(); instead of assigning it to the Worker parameter.

    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());
            }
    }

The thread for the Worker is probably using the worker instance passed to the Server constructor, so the Server needs to assign its own worker reference to that same Worker object.

I'm assuming that queue is an instance of some class that implements the Queue interface, and that (therefore) the poll() method doesn't block.

In this case, you simply need to instantiate a single queue object that can be shared by the two threads. The following will do the trick:

Queue<String> queue = new LinkedList<String>();

The LinkedList class is not thread-safe, but provided that you always access and update the queue instance in a synchronized(queue) block, this will take care of thread-safety.

I think that the rest of the code is correct. You appear to be doing the wait / notify correctly. The worker thread should get and print the message.

If this isn't working, then the first thing to check is whether the two threads are using the same queue object. The second thing to check is whether processData is actually being called. A third possibility is that some other code is adding or removing queue entries, and doing it the wrong way.

notify() calls are lost if there is no thread sleeping when notify() is called. So if you go notify() then another thread does wait() afterwards, then you will deadlock.

You want to use a semaphore instead. Unlike condition variables, release()/increment() calls are not lost on semaphores.

Start the semaphore's count at zero. When you add to the queue increase it. When you take from the queue decrease it. You will not get lost wake-up calls this way.

Update

To clear up some confusion regarding condition variables and semaphores.

There are two differences between condition variables and semaphores.

  1. Condition variables, unlike semaphores, are associated with a lock. You must acquire the lock before you call wait() and notify() . Semaphore do not have this restriction. Also, wait() calls release the lock.
  2. notify() calls are lost on condition variables, meaning, if you call notify() and no thread is sleeping with a call to wait() , then the notify() is lost. This is not the case with semaphores. The ordering of acquire() and release() calls on semaphores does not matter because the semaphore maintains a count. This is why they are sometimes called counting semaphores.

In the javadoc for wait(), it is stated that

 The current thread must own this object's monitor. 

Given my inexperience with threads I am not exactly certain what that means, but I have tried instantiating the queue from the worker thread with no success.

They use really bizarre and confusing terminology. As a general rule of thumb, "object's monitor" in Java speak means "object's lock". Every object in Java has, inside it, a lock and one condition variable ( wait() / notify() ). So what that line means is, before you call wait() or notify() on an object (in you're case the queue object) you much acquire the lock with synchronized(object){} fist. Being "inside" the monitor in Java speak means possessing the lock with synchronized() . The terminology has been adopted from research papers and applied to Java concepts so it is a bit confusing since these words mean something slightly different from what they originally meant.

The code seems to be correct.

Do both threads use the same queue object? You can check this by object id in a debugger.

Does changing notify() to notifyAll() help? There could be another thread that invoked wait() on the queue.

OK, after some more hours of pointlessly looking around the net I decided to just screw around with the code for a while and see what I could get to. This worked:

private static BlockingQueue<String> queue;

private BlockingQueue<String> getQueue() {
    if (queue == null) {
        queue = new LinkedBlockingQueue<String>();
    }
    return queue;
}

As Yanick Rochon pointed out the code could be simplified slightly by using a BlockingQueue instead of an ordinary Queue , but the change that made the difference was that I implemented the Singleton pattern.

As this solves my immediate problem to get the app working, I'll call this the answer. Large amounts of kudos should go to @Fly and others for pointing out that the Queue instances might not be the same - without that I would never have figured this out. However, I'm still very curious on why I have to do it this way, so I will ask a new question about that in a moment.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM