Most Sample Code from "Java How to Program, Sixth Edition" from Deitel and Deitel
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:
Notice:
class NumberThread extends Thread
numberThread
's are created, numbered, and started through the following syntax:
NumberThread numberThread = new NumberThread( i ); numberThread.start();
this
in the constructor function is used to refer to the class's variable count
numberThread.start()
is called, the run
function defined in the NumberThread
class is invokedrun
function, therefore, specifies the details of what you want the thread to do Notice:
class NumberThread extends Object implements Runnable
run
method is defined (it is specified in the Runnable
interface)NumberThread numberThread = new NumberThread( i ) ; Thread thread = new Thread( numberThread ) ; thread.start() ;
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.
The following two files shows Deitel's approach to creating threads:
Notice the following about PrintTask.java
:
PrintTask implements Runnable
therefore has the run
function defined ava.util.Random
is imported so that you can randomly generate numbersThread.sleep (sleepTime);
Notice the following about RunnableTester.java
:
PrintTask task1 = new PrintTask ( "thread1" );
ExecutorService threadExecutor = Executors.newCachedThreadPool();
threadExecutor.execute ( task1 );
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".
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:
set
and get
functions.buffer
), which is shared by the producer and consumer threads. Defines the set
and get
functions from the Buffer interface. sharedLocation.get()
to read 10 integers from the shared buffer.sharedLocation.set(count)
to write integers 1 through 10 to the shared buffer.sharedLocation
. Starts the Producer and Consumer threads. Notice that the sharedLocation
buffer is sent as an argument to both the Producer and Consumer. They are, effectively, using the same buffer to read from and write to. 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
:
occupied
is true
), then wait (and, as a consequence, release the lock). finally
block) for the get
:
occupied
is false
), then wait (and, as a consequence, release the lock). finally
blockThis 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:
occupiedBuffers
-- counts the number of integers that are written in the buffer but haven't been readwriteIndex
-- the array index used to write a new numberreadIndex
-- the array index used to read a new number set
, if the occupiedBuffers
= 3 (all three slots have been written to), then the producer will have to waitget
, if the occupiedBuffers
= 0 (nothing has been written recently), then the consumer will have to wait
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:
ArrayBlockingQueue
object that stores three Integer
objects.set
function, integers are put
onto the Queue. If the Queue already has three Integers, then put
will automatically block until there is room in the buffer to add more Integers.get
function, integers are take'
n off of the Queue. If the Queue is empty, then take
automatically blocks until there is an element in the buffer to remove. 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.
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 callObject
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). Methodswait
,notify
andnotifyAll
are inherited by all classes from classObject
.
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:
synchronized
for set
and get
wait()
call to put the the consumer or producer in wait statenotify()
call to signal to consumer or producer that it should move from the wait state to the blocked state