COMP 310

Threading Issues

    Current Home  Java Resources  Eclipse Resources

This page is a compilation of tips and traps regarding threads in Java.   If you have seen an issue that is not covered here that you think should be included, please tell the course staff right away!

Links:


GUI Thread Deadlocking in Producer-Consumer or Data Synchronization Scenarios

In order to gain access to data created on the GUI thread, it is common to use either producer-consumer constructs, e.g. with a BlockingQueue or use some sort of thread synchronization mechanism such as a CountDownLatch to signal a waiting non-GUI thread that the GUI thread-created data is ready for use.

Here's a typical scenario:


MyViewObject viewObj;  // field referencing a GUI object

// code in some method running on 'ThreadX'...
    // viewObj not yet instantiated
	CountDownLatch latch = new CountDownLatch(1);  // A BlockingQueue could have been used instead.
	
	// Need to make the view object on the GUI thread, so dispatch to it:
	SwingUtilities.invokeLater(() -> {    // invokeAndWait() has will deadlock if ThreadX is the GUI thread!
	   // do whatever needs to be done on the GUI thread
	   viewObj = new ViewObject();  // make the view object
	   latch.countDown();   // tell the waiting thread that the view object is ready.   With a BlockingQueue, the new instance is put into the queue.
    }); 
 
 	// wait for the view object to be ready
 	try {
 	    latch.await();   // If using a BlockingQueue, the viewObj field gets set here.
 	catch(Exception err) {
 	   // code elided...
 	}
 	 
 	// Can now work with the viewObj. 

The above code works just fine if ThreadX is NOT the GUI thread! If ThreadX is the GUI thread, the above code will deadlock!

The problem is, can you be sure a priori, that ThreadX is not the GUI thread?    In the end, you must program defensively.

Warning:  The dispatching to the GUI thread above may not be readily obvious in one's code as the invokeLater() may be buried inside of other method calls!    It is very important to document if a method is dispatching to other threads, particularly the GUI thread!

There are many possible ways to deal with this situation.  Here's two possible defenses:  one that solves the problem from the producer side and one from the consumer side.

Defense #1:   Don't dispatch to the GUI thread if you are already on it.

This technique is useful if the code that uses the viewObj is ok to be run on the GUI thread, which is often NOT true, especially if networking is involved.   Also, one may not have access the the GUI thread dispatching code if it is buried inside of opaque methods (e.g. someone else's code).


MyViewObject viewObj;  // field referencing a GUI object

// code in some method running on 'ThreadX'...
    // viewObj not yet instantiated
	CountDownLatch latch = new CountDownLatch(1);  // A BlockingQueue could have been used instead.

	// Express the GUI thread work as a command:
	Runnable cmd = () -> {    
	   // do whatever needs to be done on the GUI thread
	   viewObj = new ViewObject();  // make the view object
	   latch.countDown();   // tell the waiting thread that the view object is ready.   With a BlockingQueue, the new instance is put into the queue.
    };
    
    if(SwingUtilities.isEventDispatchThread()) {  // check what thread this is
        // On the GUI thread already, so just run the cmd directly.
        cmd.run();  // cmd is running on GUI thread 
    }
    else {
        // Not on GUI thread, but need to make the view object on the GUI thread, so dispatch to it:
 		SwingUtilities.invokeLater(cmd);  // run the cmd on the GUI thread.  invokeAndWait() might work here but is inherently more prone to deadlocking.
 	}
	
 	// wait for the view object to be ready -- this still works if cmd was run on this thread, i.e. this thread is the GUI thread.   
 	try {
 	    latch.await();   // If using a BlockingQueue, the viewObj field gets set here.
 	catch(Exception err) {
 	   // code elided...
 	}
 	 
 	// Can now work with the viewObj. But note that this might be taking place on the GUI thread!

 

Defense #2:  Always work with the result (i.e. the consumer-side) on a new non-GUI thread

This technique is useful when the work that needs to be done after the viewObj is created cannot be done on the GUI thread.   It has the drawback however of having to spawn a new thread which could be problematic from some code logic.


MyViewObject viewObj;  // field referencing a GUI object

// code in some method running on 'ThreadX'...
    // viewObj not yet instantiated
	CountDownLatch latch = new CountDownLatch(1);  // A BlockingQueue could have been used instead.
	
	// Need to make the view object on the GUI thread, so dispatch to it.   Doesn't matter if this is or isn't the GUI thread.
	SwingUtilities.invokeLater(() -> {    // invokeAndWait() will deadlock if ThreadX is the GUI thread!
	   // do whatever needs to be done on the GUI thread
	   viewObj = new ViewObject();  // make the view object
	   latch.countDown();   // tell the waiting thread that the view object is ready.   With a BlockingQueue, the new instance is put into the queue.
    }); 
 	
 	// Dispatch the consumer-side code onto a new, non-GUI thread: 
 	(new Thread(() -> {
 	    // wait for the view object to be ready 
 	    try {
 	        latch.await();   // If using a BlockingQueue, the viewObj field gets set here.
 	    catch(Exception err) {
 	       // code elided...
 	    }
 	    
 	    // Can now work with the viewObj.   This is guaranteed to NOT be on the GUI thread.
 	}).start();
 	// Most likely, there is nothing more that can be done on this thread, so exit this method.
 	// It is especially important to exit here if this is indeed the GUI thread so as to enable the invokeLater() to run. 
 }
 

The choice of defensive technique depends on many factors such as what sorts of processing is being done, whether or not one has access to the GUI thread dispatching code and/or whether or not the processing can run on a completely new thread.

 


Serializing Producer-Consumer Systems

Typically, the consumer side of a producer-consumer system will run on its own thread because it is required to block while waiting for the nexted produced entity for it to consume.   A common implementation of the consumer side may look something like this:

(new Thread(() -> {
    while(true) {
        MyValue aValue = aBlockingQueue.take();
        // process aValue
    }
})).start();

What happens if this code is in a serializable object and one transmits the object to another computer? 

First, remember:   Thread objects are NOT Serializable!   This is not surprising because the implementation of a Thread object is highly system-dependent.  

Luckiliy, in the above code, no reference to the Thread object is ever kept so that even if the surrounding object is serialized, the Thread object itself is not serialized.     That's good....sort of.     The problem with not serializing the Thread is that when the producer-consumer system is deserialized on the receiving end, there is no Thread to run the consumer side!    That is, the producer-consumer system will serialize and deserialize without any errors, but after deserializing, the consumer will never consume anything the producer side produces because the consumer side simply isn't running.

This means that the consumer threads must be explicitly re-made and started after deserialization.

Generally, the blocking queues and other objects (e.g. RMI stubs when transmitting data packets) with which the consumer side works, are all readily accessible as fields of the surrounding object or as stored entities in some sort of collection.     If this is not true, adjust your design so that it is.

The most convenient time to re-create the consumer threads is right after the object deserializes.   This point in time is easily accessible in the  readObject() method used in deserializing process (see the discussion of using the readObject() method for re-initializing transient fields).

The above code can be restructured to look something like this:

/**
 * Encapsulate the individual consumer thread creation process to eliminate code duplication.
 * This method is used both during the initial creation of the consumer-side
 * as well as during the deserialization process.
 * This method takes the blocking queue and any other objects that the consumer thread needs.
 */
private void makeConsumerThread(BlockingQueue bq, ...) {
    (new Thread(() -> {
        while(true) {
            MyValue aValue = bq.take();
            // process aValue
        }
    })).start();
}

/**
 * This method is automatically run as part of the deserialization process.
 */
private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
    stream.defaultReadObject();   // Deserialize all the fields of the host object as normal
    
    // Loop over all the blocking queues and re-create the consumer threads. 
    // Note that your collection may be more complicated than this, e.g. a Map of some sort.
    Collection_of_BlockingQueues.forEach((bq) -> {
        bq.clear();  // probably a good idea to clear the queue before starting the thread just in case something was left in it.
    	makeConsumerThread(bq, ...); // don't forget to pass in any other parameters the thread creation needs.
    });
}

Now all the consumer threads will be automatically recreated and restarted when the host object is deserialized and the producer-consumer system will work as normal!

 

 

 

 

 


© 2017 by Stephen Wong