Tutorial 15 - Threads and Serialization
A thread is the flow of execution of a single set of program statements. Multithreading consists of multiple sets of statements which can be run in parallel. Although a single processor can run only one thread at a time, strategies such as timeslicing and interrupt methods (depending on the scheduling method) simulate threads running simultaneously.
A daemon thread is a thread that runs for the benefit of other threads. Daemon threads run in the background and do not prevent a program from terminating. For example, the garbage collector is a daemon thread. Daemon threads belong to the system and not to the process that spawned them. A program can include a mixture of daemon and non-daemon threads. The Java virtual machine will exit when only daemon threads remain.
Asynchronous treads run independently of each other. Synchronous treads can exchange messages with each other or wait for an action to occur in other thread(s).
Serialization is the process of writing the state of objects such as records or trees to a byte stream. It can be used to save state variables or to communicate through network connections.
The Thread Class
The Thread Class allows multitasking (ie running several tasks at the same time) by instantiating (ie creating) many threaded objects, each with their own run time characteristics. Tasks that slow the processor can be isolated to prevent apparent loss of GUI response. Inner classes are used to set up multiple threads in a utility.
One way to create threads is to extend the Thread class and override the run() method as in:
class HelloThread extends Thread
{
public void run()
{
for (int x=0;x<100; ++x)
System.out.print(" Hello ");
}
}
However if you need to inherit from another class such as when using the applet class, you can implement a Runnable interface instead and write the required run() method as in:
class HelloThread implements Runnable
{
Thread t = new Thread(this);
t.start();
public void run()
{
for (int x=0;x<100; ++x)
System.out.print(" Hello ");
}
}
Thread object methods are used on instantiated thread objects to control the thread appropriately. These methods include currentThread(), getName(), getPriority(), isAlive(), join(), run(), setName(string), setPriority(int), sleep(longInt) and start().
Note: Some older methods such as stop(), suspend() and resume() have been deprecated as they could cause system instability or hangup! Read Oracle's recommendation on fixups. One easy way of stopping a thread is making the run() method into a while loop based on a logical that can be set to false as in:
public void run()
{
while (okToRun==true)
{
// do the run time stuff here
}
}
Assigning Priority
Priority is thread ranking. Some threads can either run for a longer timeslice or run more often (depending on the operating system). Peers (or equals) get the same time/number of runs. Higher priority threads interrupt lower priority ones. Priority is set from constants MIN_PRIORITY (currently 1) to MAX_PRIORITY (currently 10) using the setPriority(int) method. NORM_PRIORITY is the midrange value (currently 5). These are defined in the Thread class.
Threads and Swing
Most methods in Swing are not thread-safe! All activities that interact with Swing components occur on a single thread known as the event dispatch thread. There are several occasions that require enforcing the event dispatch thread:
- using an asynchronous progress bar
- using a server (with listener) that pushes data
- tasks that require long processing times (eg. database queries, invoking remote methods, etc.)
Example: Digital Clock
An example of using asynchronous threads for animation is the applet clock.htm. The output could also be displayed in the browser's status line by using the showStatus(string_msg) method.
Example: Animation
Animation is the appearance of life or movement in an object. It is achieved by redrawing screens with objects shifted slightly. Realistic movement is achieved by appropriate choices of shift and refresh rate.
AniMain.java is a simple Java game that involves animation. Read the source code for techniques on using threads to make objects appear to have life by rapid screen redraws.
Animation flicker is caused during screen repaints as fragments that do not change are temporarily cleared and then rewritten. This occurs during the update() method called by repaint(). But you can override update() to reduce flicker by either: not clearing the screen or redrawing only what you have to. A better way to avoid flicker is to double buffer the image so that the next one is 'preloaded'. This is more complex and costs memory but gives a much better rendering.
Example: Splash Screen
Splash screens are opening screens that many commercial programs use to 'spiff' up their appearance. Once a splash has run for a set time or when user interrupted it is tossed away and the actual application appears. Program ending credits can be rolled in the same way but done at application close time instead.
SimpleThread.java is an implemented class and ExtendThreadClass.java is an extended java class that contain a independent timer which can be easily adapted into a splash screen. For a splash screen to be interruptible, synchronization must occur.
Synchronization
Multi-thread synchronization is required when two or more threads need to share a resource. A monitor (aka semaphore) provides a mutually exclusive lock for an object. Java provides the synchronized keyword as the key that locks/unlocks an object's monitor. It can be used as a class or method modifier or as a statement (very localized). To guarantee that a variable is threadsafe (ie. not shared between threads), it can be marked as volatile. Any long running method should not be synchronized as it would become a traffic bottleneck.
Note: Each object has only one monitor. Synchronized methods may need to be contained in different classes!
SyncThread.java is an example of an interruptible timer that can be adapted into a splash screen if wanted. It also illustrates how wait() and notify() can be used to replace suspend() and resume().
One example of resource sharing is a FIFO buffer with a producer thread adding data when it can and a consumer thread taking it when available. Synchronization is needed so that the data is accessed by only one thread at a time. In addition the queue manager must prevent overflow and underflow. These errors are managed by the wait() and notify() methods. Download fifo.java for a simple example of a synchronized queue.
Starvation occurs when one or more threads in your program is blocked from gaining access to a resource and thus cannot make progress. Deadlock occurs when two or more threads are waiting on a condition that cannot be satisfied. Deadlock most often occurs when two (or more) threads are each waiting for the other(s) to release a resource the other is/are waiting for. A classic example of deadlock is the dining philosophers problem. A simple demo can be found here.
Piped Streams
Piped streams are io streams whose sources and destinations are threads. Objects created from PipedInputStream (8-bit) and PipedReader (16-bit) classes read data from PipedOutputStream and PipedWriter classes. PipedAnalyzer.java is an example of the use of piped streams.
Object Stream Serialization
We have already seen how objects are used to encapsulate associated information such as record fields into a structure and how they can be processed as collections. Object streams read and write objects as their basic unit. No user coding is needed to structure the I/O format. Note that any object that is going to use I/O requires implementation of the Serializable interface. The object stream constructors are ObjectInputStream(InputStream inStreamObj) and ObjectOutputStream(OutputStream outStreamObj). The object methods are readObject() and writeObject(obj). Methods for primitives use readXX() and writeXX() where XX is a datatype.
Serialization allows information to persist or 'live' outside the program that created it. If a property in a serialized object is not to persist, it must be marked with the transient keyword.
Note: Serialized streams are not encrypted and are easy to modify. If the data requires high security, you should test property values at read time before importing into your application classes. This is especially true if the internet is used. Alternatively you can use the Externalizable interface to add encryption and compression facilities.
objUpdate.java is a working io system that uses object streams to read, write, update and append file objects. You can use it as a start point and add your own properties and methods between the loadFile() and saveFile() methods.
Tutorial Source Code
Obtain source for clock, AniMain, SimpleThread, ExtendThread, SyncThread, fifo, PipedAnalyzer, objUpdate, etc. here.