Synchronizing Threads that Share Data

COMP 310  Java Resources  Eclipse Resources

Go to


Synchronizing the operations of threads when their processes are inter-dependent is fraught with problems such as race conditions and deadlocking

Rule #1: Decouple!!

The best solution is not to have the problem come up in the first place! Don't couple things, such as threads, together unless absolutely necessary. If two threads are independent of each other, there is no need to synchronize them.

The need for synchronization is an indication of coupling.

 

Synchronizing shared data

One of the most common problems is occurs when a piece of data is created or made available by one thread (a "producer") but is needed by another thread (a "consumer").

Note that the notion of "data being ready" is more than just a simple passing of values but could also apply situations such as

Failure to recognize this problem can lead to race conditions where the consumer thread will not have the data when it needs it.

Improper solutions to this problem can lead to deadlocks where the locking of the consumer thread can also lock the producer thread.

Important Rules:

 

Synchronization Techniques:

Here are some possible techniques that can be employed.

Consumer as a Function

This is technique is very nice in that it doesn't require blocking a consumer thread. Here, the consumer is represented as a function, perhaps one that takes the shared data as an input parameter or simply accesses an accessible data location.

When the data is available, the producer simply invokes the consumer function, preferably on its own thread so as not to tie up the producer thread.

The disadvantage of this technique is that the consumer process can only be started by the producer process. Multiple values of the produced data can be managed though usually at the expense of spawning new consumer threads for each new value.

Coding tips:

 

Consumer as a Blocked Thread

This technique is useful when

Here, the a consumer thread is started but then blocked until the producer makes the data available. Once unblocked, the consumer thread can access the data and process it.

The disadvantage of this technique is that it requires blocking a thread which could lead to deadlocking problems if one is not very careful.

To block the consumer thread use:

 

Coding tips:

 

 

Producer Loops When Retrieving Data From A Source Does Not Support Blocking

This is a very common situation when retrieving data from a data storage entity such as a database or dictionary, which don't typically support blocking a thread attempting to retrieve data from them. That is, these data storage entities will just have a "get" type method that returns null if the desired data was not found. Some data storage entities will have a "contains" type method that will return a boolean value as to whether or not the desired data is contained in the data storage.

In such, the producer process is actually the process that is attempting to retrieve the data from the data storage when it become available.

Because thread blocking is not supported, a "polling" technique is usually required. This is just a loop that checks to see if the data is present and if not, sleeps for a bit and then checks again. This process repeats until the data is found. When the data is found, then the consumer process can be invoked using the techniques described above.

Pseudo-code:

Data storage only has "get" functionality:

// This whole process should be run in its own thread!

// key = the key for the desired data

while(true) {   
   data = datastore.get(key)
   if(null==data) {
      // required try-catch elided
	   Thread.sleep(10)   // milliseconds -- use a value that is on the order of how fast the data should appear.  Too fast uses too much CPU resources.
   }
   else {
        break;
   }
}
// present the data to the consumer process, e.g. save to field, call consumer function, count down the latch, offer to blocking queue, etc.

 

Data storage has "contains" functionality:

// This whole process should be run in its own thread!

// key = the key for the desired data

while(!datastore.contains(key)) {
   // required try-catch elided
	Thread.sleep(10)   // milliseconds -- use a value that is on the order of how fast the data should appear.  Too fast uses too much CPU resources.
}
data = datastore.get(key)
// present the data to the consumer process, e.g. save to field, call consumer function, count down the latch, offer to blocking queue, etc.

 

 

Producer Processes During GUI Component Factory Operations

GUI components are often created as part of a factory process running on the GUI thread. However, processes on other threads are often dependent on those GUI components being ready for usage.

Here, the producer process is the one that detects when the GUI component has been instantiated and is as ready as possible to be used.

Since the GUI component factory must return the component for it to be installed into the host system, a producer process must wait until the very last moment before initiating the consumer process. In general, this means waiting until after the components have been instantiated and configured but just before they are returned as the output of the factory. The above described techniques for the consumer process can be used.

Tips:

 

 

 

 

© 2021 by Stephen Wong