Most Sample Code from "Java How to Program, Sixth Edition" from Deitel and Deitel


Note before Working with Threads

Please do not experiment with threads and processes on hercules or any shared-server machine (Sun-Ray's too). If you accidentally create too many processes or threads and it crashes the system, you may lose your account for the rest of the semester.

It is best to experiment with these programs on your home computer running Windows or a machine running Linux. On hercules or the Sun Rays, you can ssh to some Linux machines:


Two Methods of Creating Threads (from Sun's On-line Course)

Extending the Thread Class

NumberThreadDemo1.java

Notice:

Implementing Runnable

NumberThreadDemo2.java

Notice:

This second method of creating threads seems to create an additional step; yet, Sun recommends this method over the first. Why?

Consider that Java can not do multiple inheritance, but you can implement several interfaces.

As an aside, the syntax to implement several interfaces is to provide a comma-separated list of interface names after the keyword implements in the class declaration.


Another Method of Creating Threads (from Deitel)

The following two files shows Deitel's approach to creating threads:

Notice the following about PrintTask.java:

Notice the following about RunnableTester.java:

Try using

Executors.newFixedThreadPool(2);

instead of newCachedThreadPool(). Notice that only two threads can be run at one time. As soon as one is finished running, the third can start. The third is put in a queue until there is space in the "pool".


Synchronization

A problem with threads occurs when two threads are trying to access shared data. If there is no synchronization between the threads, then unpredictable results will occur. The following files show what happens when the producer writes numbers 1 through 10 to a shared buffer and the consumer reads from the buffer. There is no synchronization in this case:

An overview of the different files:

When this code runs, the goal of the consumer is to add up the integers that the Producer has written to the buffer. The integers are 1 through 10. The result should be 55.

You may end up with 55 as the sum, but more than likely, you will end up with another result. This is because the Producer and Consumer are not synchronized. The Consumer may consume several of the same integer or the Producer may overwrite numbers that the Consumer has never read.


Now that we have considered the situation where there is *no* synchronization, let's examine the synchronized approach.

The following two files are used with the Buffer.java, Consumer.java, and Producer.java from above:

Because we have two versions of SynchronizedBuffer, the first thing you can do is:

 cp SynchronizedBuffer_a.java SynchronizedBuffer.java 

Notice the following in SynchronizedBuffer.java:

We are importing some new classes to deal with locks. There will only be one lock shared between the set and get functions.

The idea in English is the following:

for the set:

for the get:


Circular Buffer

This code is really a lead into the ArrayBlockingQueue code that is coming up.

The idea behind circular buffers is that you might want to allow the producer and consumer to write/read multiple values. Why would you want to do that? Well, for instance, when you are downloading a movie, you may want to have one thread which is downloading while another one is playing. You want to be able to store several bytes of data (not just one at a time) so that the consumer can read several bytes at a time and play the movie back smoothly.

The code for the circular buffer is below. Again, this code goes with Buffer.java, Consumer.java, and Producer.java:

The differences in this code from the SynchronizedBuffer code are the following:

 


ArrayBlockingQueue

ArrayBlockingQueues are something new in Java version 5 (or 1.5).

The following code does the same thing as the CircularBuffer example, except that it uses an ArrayBlockingQueue:

Notice that the BlockingBuffer code is reduced from the CircularBuffer example from above. The following pieces of code are essential:

private ArrayBlockingQueue<Integer> buffer;

...
    buffer = new ArrayBlockingQueue<Integer>( 3 );
...
       buffer.put(value);

...
       readValue=buffer.take();

A brief description follows:

The synchronization is handled by the ArrayBlockingQueue. The put and take are synchronized to one another, but the corresponding output may not be synchronized; thus, you may get Producer and Consumer output statements printed out of order.


Sun's Approach to Synchronization

This approach is in the on-line course, but is also discussed in Deitel's book (page 1093). Deitel refers to this approach as "Monitors and Monitor Locks". The keyword synchronized is used in this approach.

Deitel has the following to say (on page 1094) about this approach to synchronization.

...If there are several synchronized statements trying to execute on an object at the same time, only one of them may be active on the object at once--all the other threads attempting to enter a synchronized statement on the same object are placed in the blocked state.

The blocked state ... transitions to and from the runnable state. When a runnable thread must wait to enter a synchronized statement, it transitions to the blocked state. When the blocked thread enters the synchronize statement, it transitions to the runnable state.

When a synchronized statement finishes executing, the monitor lock on the object is released and the highest-priority blocked thread attempting to enter a synchronized statement proceeds. Java also allows synchronized methods. A synchronized method is equivalent to a synchronized statement enclosing the entire body of a method.

If a thread obtains the monitor lock on an object and then determines that it cannot continue with its task on that object until some condition is satisfied, the thread can call Object method wait, releasing the monitor lock on the object. The thread releases the monitor lock on the object and waits in the waiting state while the other threads try to enter the object's synchronized statement(s). When a thread executing a synchronized statement completes or satisfies the condition on which another thread may be waiting, it can call Object method notify to allow a waiting thread to transition to the blocked state again. At this point, the thread that transitioned from the wait state to the blocked state can attempt to reacquire the monitor lock on the object. Even if the thread is able to reacquire the monitor lock, it still might not be able to perform its task at this time--in which case the thread will reenter the waiting state and release the monitor lock. If a thread calls notifyAll, then all the threads waiting for the monitor lock become eligible to reacquire the lock (that is, they all transition to the blocked state). Remember that only one thread at a time can obtain the monitor lock on the object--other threads that attempt to acquire the same monitor lock will be blocked until the monitor lock becomes available again (i.e., until no other thread is executing in a synchronized statement on that object). Methods wait, notify and notifyAll are inherited by all classes from class Object.

The code using this synchronized method is:

This code can be used with SharedBufferTest2.java. To use SynchronizedBuffer_b.java, you can:

cp SynchronizedBuffer_b.java SynchronizedBuffer.java

Items to note are the following: