CSE5910 : Multimedia Programming in Java

Lecture : Threads

In the previous lecture:

In this lecture:



References.

The Sun Java site has a tutorial on Threads from which some of the material in ths lecture is derived.
Other material is based on Flanagan's Java Examples in a Nutshell.

What are threads?

An set of instructions executing sequentially to carry out some task within a program runs in a thread.

Many programs seem to follow a single line of execution, they have a single task that is executed in one thread. This single sequence of instructions is executed from start to finish, following loops, method calls and conditional branches.

In many cases, it is useful to have several instruction sequences that execute independently and in parallel. Each of these executes in a separate thread.


How are threads executed?

On a machine with a single CPU, instructions can't actually be executed in parallel but the operating system can switch between threads part way through their execution to give other threads some resources for awhile.

This is time slicing.

Time slicing can work effectively, even for many threads, because often the CPU isn't required by a particular thread. For instance, a thread may sit and wait for a user to click on the GUI whilst another thread repaints the screen.

 

On a machine with multiple CPUs, each thread can be executed on its own dedicated processor and can wait or execute independently of the others (provided it doesn't need to wait for a result being computed by another thread).

This is multiprocessing.


Threads and Processes

Threads executing concurrently share data, i.e. they can act on the same data-structures and in the same address space.

  • One thread needs to be programmed so that it doesn't read from a data-structure whilst another is writing to it.

  • Programmers need to be careful that if a thread is working with a value just read from a data-structure, another thread doesn't change that value behind its back unless it is safe to do so.

  • Threads can however communicate with one another via these data-structures with very little over-head.

Threads are different to processes.

Each time you run a program (e.g. from a shell or desktop GUI) you are starting a new process.

It is also possible for one process to start up other processes (see the UNIX fork( ) command).

Two processes executing concurrently usually act independently, on independent data in separate address spaces.

  • Processes can communicate with one another via Inter Process Communication (IPC) resources (pipes and sockets) but there is usually some over-head involved.

  • More detail on spawning independent processes is outside the scope of the present discussion.

Why are threads used in multimedia software?

To keep any time-critical aspects of a program running smoothly, for instance:


Creating Threads in Java

One way to work with Java Threads is to implement the Runnable interface.

This interface only requires the implementation of a single method, run( ) that will specify what a Thread is to execute.

public class ThreadTester implements Runnable
{
   public void run ( )
   {
      System.out.println("Hello from a thread!");
   }
   
   public static void main (String args[])
   {
      ThreadTester myThreadTester = new ThreadTester( );
      Thread myThread = new Thread (myThreadTester);

      myThread.start( );
   }
}

$: java ThreadTester
Hello from a thread!

$:

In any Java program, the main() method runs in a Thread.

In the code above, once the additional Thread myThread is started, the code in myThreadTester's run() method is executed. When the method ends, this newly made Thread stops running.


Multiple threads example

public class MultiThreadTester implements Runnable
{
public void run ( )
{
for (int i=0; i<5; i++)
{ compute( ); }
}

public static void main (String args[])
{
MultiThreadTester myThreadTester = new MultiThreadTester( );

Thread myThread1 = new Thread (myThreadTester);
Thread myThread2 = new Thread (myThreadTester);

myThread1.setPriority(Thread.NORM_PRIORITY);
// normal priority myThread2.setPriority(Thread.MAX_PRIORITY); // highest priority
      myThread1.start( );
      myThread2.start( );
   
      for (int i=0; i<5; i++)
      { compute( ); }
   }  

   // ThreadLocal provides an object accessed by set() and get() unique for each thread.
   // We use it here to keep track of how many times each thread is called.
   static ThreadLocal numCalls = new ThreadLocal();
   
   // All the threads will call this method from the loop inside run() and main()
   static synchronized void compute ( ) // synchronized prevents multiple simultaneous access (see below)
   {
      Integer n = (Integer) numCalls.get();
      if (n == null) n = new Integer(1);
      else           n = new Integer(n.intValue() + 1);
      numCalls.set(n);
     
     // Automatically generated names are of the form: Thread-#
     System.out.println(Thread.currentThread().getName() + ": " + n);
     
     try
     {  // Sleep a random amount of time (simulate i/o or network delays etc.)
        // Other threads (even those of lower priority) can run during this sleep time
        Thread.sleep((int)(Math.random() * 100 + 1));
     }
     catch (InterruptedException e) { }
     
     // Don't starve other threads of equal priority, give them some time
     Thread.yield();
   }
}

$: java MultiThreadTester
Thread-1: 1
main: 1
Thread-0: 1
Thread-1: 2
main: 2
Thread-0: 2
Thread-1: 3
main: 3
Thread-0: 3
Thread-1: 4
main: 4
Thread-0: 4
Thread-1: 5
main: 5
Thread-0: 5

$:

The output shows the three threads: two extras and the usual main() thread.

Since each thread "yields" to the others after each call to compute( ) the numbers are printed out in order.

The example shows the use of the thread and some of its different methods. Also note the use of a ThreadLocal object to store a variable that is unique to each thread. Usually threads share variables... this can be dangerous (see the next example).

Homework:

Experiment with the code above.
How can you see the impact of yielding the thread?
What impact do different thread priorities or adjustments in sleep time make?


Thread Safety and Synchronizing Threads.

When using threads the programmer needs to be certain that each operates on a current, correct set of variables.

An example of the problem:

  1. Imagine a class that stores a group of integers in an array, data.
  2. The class keeps track of the number of stored integers in a variable count.
  3. Lets say the current state is data = [2, 7, 1, 5], count = 4.
  4. Imagine a thread1 that adds an integer: data = [2, 7, 1, 5, 6]
  5. thread1 reads count then increments it and...
  6. Imagine a thread2 that now clears the data =[ ] and sets count=0 then exits. (Note: thread1 has already read count.)
  7. Now thread1 writes the new value of count it has calculated count=5 then exits.
  8. The state of the software is: data = [ ], count = 5.
  9. The programmer looks like the guy in the image at left: tangled in a lot of threads and jobless.

The way to avoid this is with synchronized methods in the class.

A method that is declared synchronized must obtain an exclusive lock on an instance before it calls a method. If one is not available, the thread blocks until it obtains a lock.

Note. Synchronized methods operate slowly due to the need to obtain a lock. Only make methods synchronized when necessary.

Here's how you could write this class to be thread-safe (see Flanagan, Java Examples in a Nutshell, p88):

public class ThreadSafeIntList
{
protected int[ ] data;
protected int count;
private static final DEFAULT_CAPACITY = 8;

public ThreadSafeIntList( )
{
data = new int [DEFAULT_CAPACITY];
}

public ThreadSafeIntList(ThreadSafeIntList original)
{
// Synchronize the original to be sure it doesn't change during the copy
synchronized(original)
{
this.data = (int[ ]) original.data.clone();
this.count = original.count;
}
}

// Synchronize methods so that the object doesn't
// change while a value is being read, written or returned..
.

public synchronized int size( )
{ return size; }

public synchronized int get(int index)
{
if (index<0 || index >= size)
{ thrown new IndexOutOfBoundsException(String.valueOf(index)); }

return data[index];
}

public synchronized void add(int value)
{
if (size == data.length) setCapacity(size*2);
data[size++] = value;
}

public synchronized void clear( )
{ size = 0; }

public synchronized int[ ] toArray( )
{
int[ ] copy = new int[size];
System.arraycopy(data, 0 , copy, 0, size);
return copy;
}

// No need to synchronize this method b/c it is only called by
// synchronized methods

protected void setCapacity(int n)
{
if (n==data.length) return;
int[] newdata = new int[n];
System.arraycopy(data, 0 , newdata, 0, size);
data = newdata;
}

}

Deadlock

Sometimes synchronizing a class can itself cause problems:

  1. Imagine thread1 and thread2.
  2. Each needs to get a lock on objectA and objectB.
  3. thread1 gets a lock on objectB.
  4. thread2 get a lock on objectA.
  5. thread1 tries to get a lock on objectA but fails (it is locked by thread2) so thread1 waits.
  6. thread2 tries to get a lock on objectB but fails (it is locked by thread1) so thread2 waits.
  7. A deadlock has occured! Soon the programmer looks like the person at left.

There is no simple solution to this problem, especially when many threads and many resources are involved.

One tactic that helps to minimise deadlocks is to ensure every thread always obtains locks in the same order as the all other threads.

  1. Imagine thread1 and thread2.
  2. Each needs to get a lock on objectA and objectB.
  3. thread1 gets a lock on objectA.
  4. thread2 tries to get a lock on objectA but fails (it is locked by thread1) so thread2 waits.
  5. thread1 gets a lock on objectB.
  6. thread1 does its thing, releases its locks on objectA and objectB then exits.
  7. thread2 gets a lock on objectA.
  8. thread2 gets a lock on objectB.
  9. thread2 does its thing, releases its locks on objectA and objectB then exits.
  10. The program has worked! Soon the programmer looks like the person at left.

 



Lecture summary:


CSE5910 Courseware | CSE5910 Lecture Notes