An interviewer can quickly learn how well you understand multithreaded programming by asking you to solve one or two classic problems involving multiple threads of execution.
| Important |
Explain the
|
This is a simple problem, but one with important performance implications for any multithreaded application.
Consider a thread that spawns another thread to complete a task. Assume that the first thread needs to wait for the second thread to finish its work, and that the second thread terminates as soon as its work is done. The simplest approach is to have the first thread wait for the second thread to die:
Thread task = new TheTask(); task.start(); while( task.isAlive() ){ ; // do nothing }
This is called busy waiting because the waiting thread is still active, but it’s not actually accomplishing anything. It’s “busy” in the sense that the thread is still executed by the processor, even though the thread is doing nothing but waiting for the second thread to finish. This actually “steals” processor cycles away from the second thread (and any other active threads in the system), cycles that could be better spent doing real work.
Busy waiting is avoided using a monitor or a semaphore, depending on what’s available to the programmer. The waiting thread simply sleeps (suspends itself temporarily) until the other thread notifies it that it’s done. In Java, any shared object can be used as a notification mechanism:
Object theLock = new Object(); synchronized( theLock ){ Thread task = new TheTask( theLock ); task.start(); try { theLock().wait(); } catch( InterruptedException e ){ .... // do something if
interrupted
} } ..... class TheTask extends Thread { private Object theLock; public TheTask( Object theLock ){ this.theLock = theLock; } public void run(){ synchronized( theLock ){ .... // do the task theLock.notify(); } } }
In fact, the
Thread task = new TheTask(); synchronized( task ){ task.start(); try { task.wait(); } catch( InterruptedException e ){ .... // do something if interrupted } } ..... class TheTask extends Thread { public void run(){ synchronized( this ){ .... // do the task this.notify(); } } }
For extra bonus points, ask the interviewer if the first thread is an event thread. Blocking an event thread is almost never acceptable, so waiting indefinitely for a lock to be released won’t cut it in a real-world application. That’s why most graphical
| Important |
Write a Producer thread and a Consumer thread that share a
|
This is one of the canonical concurrency problems. The first step is to answer the problem without using any concurrency control, and then comment on what the problems are. The algorithm isn’t very difficult when concurrency isn’t an issue. The data buffer looks like this:
public class IntBuffer { private int index; private int[] buffer = new int[8]; public void add( int num ){ while( true ){ if( index < buffer.length ){ buffer[index++] = num; return; } } } public int remove(){ while( true ){ if( index > 0 ){ return buffer[--index]; } } } }
The producer and consumer are almost trivial:
public class Producer extends Thread { private IntBuffer buffer; public Producer( IntBuffer buffer ){ this.buffer = buffer; } public void run(){ Random r = new Random(); while( true ){ int num = r.nextInt(); buffer.add( num ); System.out.println( "Produced " + num ); } } } public class Consumer extends Thread { private IntBuffer buffer; public Consumer( IntBuffer buffer ){ this.buffer = buffer; } public void run(){ while( true ){ int num = buffer.remove(); System.out.println( "Consumed " + num ); } } }
Then, somewhere in the code you start the threads:
IntBuffer b = new IntBuffer(); Producer p = new Producer( b ); Consumer c = new Consumer( b ); p.start(); c.start();
There are two problems with this approach, however. First, it uses busy waiting, which wastes a lot of CPU time. Second, there is no access control for the shared resource, the buffer: If a context switch occurs as the index is being updated, the
You may think at first that making the
add
and
remove
public class IntBuffer { private int index; private int[] buffer = new int[8];
public synchronized void add( int num ){
while( true ){ if( index < buffer.length ){ buffer[index++] = num; return; } } }
public synchronized int remove(){
while( true ){ if( index > 0 ){ return buffer[--index]; } } } }
This won’t fix anything, however. Instead, the first thread to call
add
or
remove
will prevent the other thread from even entering the other method. Nothing will happen: One of the threads will busy-wait indefinitely and the other will be permanently
public class IntBuffer { private int index; private int[] buffer = new int[8]; public synchronized void add( int num ){ while( index == buffer.length - 1 ){ try { wait(); } catch( InterruptedException e ){ } } buffer[index++] = num; notifyAll(); } public synchronized int remove(){ while( index == 0 ){ try { wait(); } catch( InterruptedException e ){ } } int ret = buffer[--index]; notifyAll(); return ret; } }
This code actually allows multiple