Into Java - Part XIX
Sweden has shown its best side this summer, the sun shines and the beach seems to be the proper place to be. However, experiencing such hard circumstances it can be nice to take refuge in front of the computer to write an article on multithreading.
Threads are most handy in GUI applications since one thread must always listen for user input. Many times, work takes a while and during that time the app seems to be frozen. Of course it is not, but while the one thread does its work it cannot also listen for user input.
Consider a web browser, while downloading an extensive article, the thread in charge will stay at that task until the end of the article. Your repeated clicks on the "Cancel" button is of no use if another thread doesn't listen for user input. That is why most modern apps are multithreaded, modern computer users crave immediate response.
We will soon know what multithreading is, how to use threads, and cover some pitfalls. We will also make use of threads in a tiny demo application.
Threads--what are they?
For a long time computers have had the capability of multitasking, that is to run several programs in parallel. But for almost as long as that, multithreading has been in use, for the reason told above. OS/2 supports one of the best schema for both multitasking and multithreading.
In short, a single processor (multi processor computers can run many tasks in parallel, but the explanation is still valid) switches from one task to another so rapidly a user does not notice it. If a program is multithreaded the CPU switches from one thread to another one, in one order or another (in fact the switching order and how it is done is up to the implementation of the JRE and may thus differ.)
Many programmers are familiar with processes and how to fork a new process. The simplified difference between a process and a thread is that a process has its own working space while a thread is kind of a lightweight process that uses its owners working space. Java uses threads and not processes, except for the main program that builds its own working space. Threads are faster and less resource hungry than processes are.
It is common practise when a discussion on multithreading takes off to warn of the side effects. Yes, there are pitfalls. I will not deal too much with them since that is a book in itself, though, I will explain a few later on.
You will find the class
java.lang. That class implements the
Runnable interface that has only one method to implement, the
run method. Let us start with
There is no point in creating an instance of the
Thread class since you cannot add much code to that class. You have to extend it and the use the inherited methods. The methods of interest are:
Now let us make a small demo.
- This is the place to put the code you are implementing, any
Threadstarts here. You never call
start. In the
Threadclass this method is an empty method that you have to override.
sleep(long milliSeconds) throws InterruptedException
- Put the thread asleep for that amount of milliseconds. During this time the thread will not ask for CPU time.
- You call this method after creating a thread instance and this method kicks off the new thread by calling
run. You never call
- Makes this thread pause and lets other threads run. Useful if you realize that this thread uses lots of CPU cycles that causes the other threads in this app to starve for cycles. Differs from
sleepin that this thread is still awake but waits for its turn.
The constructor takes two parameters, one that will be printed to the standard output and another that is the time to sleep.
run overrides the one of the ancestor class
sleep may throw an exception, we must encompass the code with a try/catch block that in fact does nothing if such an exception is thrown. These exceptions are mainly thrown on running instances from other threads within the application, but I will not go into that.
The loop will run forever, or at least until the main thread quits by your action (Ctrl+C). A line is written and then it's time for another nap.
To run this stuff we need a driver class.
We instantiate two threads and start them, one with two seconds sleeping time and the other with 1.8 seconds sleeping time. Compile and run and you will soon see the second thread take the lead and lap the first one after a while.
In fact you have three threads running, the third does nothing at all. That is the main thread that started the two secondary threads. If this was a GUI app it would likely listen for user input. But here we can make use of it by adding a few lines to the code directly after the creation of the second thread shown above.
As you can see, the loop will run for one minute and then the exit-line is executed and the program closes, including the two other threads, as they are child-threads from the main app.
Note that since
ThreadDriver does not extend
Thread there is no obvious way to get it to sleep. We then make a call to the static method
sleep in the
Thread class. Since any Java program has a thread that is the one initially calling main, this is the way to get to it.
The example above works nice, you create your own class extending
Thread and that is it. Are there any drawbacks from that? Yes, if you would like to inherit from another class instead of
Thread. Recall that Java does not support multiple inheritance, which I consider sound and thus Java avoids many nightly debugging sessions.
Consider you have a little timer ticking somewhere and you found it neat to inherit from
JLabel. Then you may use the
Runnable interface instead. Interfaces are the solid short cut to multiple inheritance.
We will remake the code above into a GUI app. Let us start with the threads.
This class inherits from
JLabel and implements the
Runnable interface. In fact it did so from the former implementation since
Runnable. One variable is added,
myThread. The constructor is extended by instantiating the thread and starting it. The start call could have been done in the former constructor too, a single
start() call within the constructor and not from the
ThreadRunner. The decision is made by the developer who knows the best time to start a new thread, though it may have been instantiated any time.
Note that we send
this as an argument to the
Thread constructor. Otherwise the thread can never know which
run method to call upon start. Try to omit
this, you will not get any warnings or errors at all from
javac, but nothing will happen to the labels, they will not even show up since the text is first set in the
run method. The thread needs a reference back to the class holding the thread.
The run method is somewhat changed, now the
setText method is called instead of writing to standard output. Otherwise nothing differs from the former implementation.
These minor changes have turned our class into one that inherits from
JLabel and in reality also from
Thread, though we had to implement the
Runnable interface and create the thread ourselves.
Now we will change the driver class a little.
The main difference from the former implementation is the lack of the method
go and the more extensive constructor. As always with GUI applications, most of the constructor stuff is related to the GUI items.
Since our threads now are
JLabel we can add them to the panel that is the container of the labels. And the constructor of
MyThread starts their respective threads, another developer does not even have to bother with that part of the code.
This time we do not bother to use the main thread, it is waiting for user input. The only input to get is the exit call, but nevertheless, someone has to do that job too <grin>.
Multithreaded applications are prone to severe dangers. Not the tiny demo we made ourselves, but consider more complex tasks. Think of a banking system holding several accounts and yours is $10,000. At a certain time you would like to deposit $500 but by a coincidence the system itself withdraws $1,000 the very same time due to a mortgage payment you have approved earlier. No-one could have foretold that coincidence, and many times it would do no harm but other times you lose $500. What could have happened?
There are a few scenarios possible but I will tell you the one you do not like. The automatic withdrawal comes first and reads the balance that is $10,000. Then your clerk comes and his computer reads $10,000, computes the new balance and writes $10,500. Unfortunately the mortgage robot comes and writes his new balance that is $9,000 and presto, $500 is gone!
Luckily banking systems do not behave this way, they block the balance until either the robot or the clerk is done and that way such an error could never happen. But think of a mere multithreaded peer-to-peer chat client where two chatters send their message simultaneously. The messages can easily get confused by writing a few lines from one and a few lines from the other to the text area.
There are mechanisms prohibiting such errors but I consider it a little bit off this installment and point you to a splendid paper by Neel V. Kumar. This paper also deals with deadlocks and livelocks, locks that can block other threads from doing their tasks. But the part on advanced multithreading is left to you. Remember that concurrent programming is a large field for research and Java offers most of the technology that field is about.
The lesson to take away is that any time a variable or object can be set by two or more threads we have to watch out. Another important lesson is that if one object must have information from another object that in its turn must have information from the first object, information that is not possible to get until the first object's request is fulfilled, then you have a deadlock. Neither one will be able to continue since they are waiting for each other's answer.
Livelocks can be the result of a hungry object accepting new tasks until it runs out of resources. The Internet DOS (Denial of Service) attacks work that way, flood a server with dummy requests until the server's buffers are overrun and it dies. From a surfer's point of view he is a victim of Denial Of Service.
newcreates a new instance of them. As soon as the code calls the
startmethod Java will do its work and transfer the thread to a "runnable" state. Execution begins.
Unless nothing happens the thread will reside there, fed by some CPU cycles from time to time by the operating system and the JRE.
But the developer is in the position of transfer the thread to the "blocked" state where it will not ask for more CPU cycles, it is really asleep. Any use of
sleep will cause that. But also any call to I/O methods, such as
readLine, will make the thread blocked until an end-of-line character is found. Hence you must not put any thread to such work, especially not the one listening for user input.
A call to stop causes the thread to die, if you do not watch out, nothing will be saved. The natural way is to let the thread die by itself when it has finished the run method and leaves it. A thread will also die if the main thread exits.
Please remember that the thread of an object does not deliver work to another thread by itself. That is, if object A (that has its own thread) makes a call to a method of object B (that also has its thread), it is not the B-thread that continues the work. It is the A-thread that visits object B and does something within B's methods and with its variables using the methods.
Many beginners think that B can do some internal heavy work while A will continue listen to a stream for example. That is not the case! If the streamlistener must be responsive, you must hand over the input to B but let B do the work with lower priority, and--hey!--multithreading is not that simple.
Next time we will make use of these new concepts and look into networking. Stay tuned.
The complete IntoJava archive (updated a few days after a new issue is out.)