Threads basics

 

  1.  Introduction.

  2. Simple loops.

  3. Thread.sleep method.

  4. Thread.yield.

  5. Thread.join (instance) method.

  6. Important note:

  7. The synchronized keyword.

  8. The lock object:

  9. Synchronized code categories.

  10. Wait and notify

  11. Join's special lock

  12. Sleep / Yield / Join vs Wait and Notify.

  13. Deadlock.

  14. InterruptedException.

Introduction

This write-up is aimed at demystifying thread programming. This is a basic thread programming article. The aim of this article is to talk about thread programming in simple terms without bringing in complex examples or applications. There is need to categorize the basics to help in remembering the api and usage scenarios and that is what this article tries to address.

 

Thread programming knowledge assimilation should be done in the following easy steps.

1)      Understanding the API for which one gets started by reading the java documentation and the java tutorials.

2)      Classifying the knowledge into 2 main sections

a)      Threads knowledge without explicit synchronization concerns (from developer's point of view) specifically- sleep, yield and join.

b)      Threads knowledge related to explicit synchronization concerns (from developer's point of view) specifically - synchronized keyword, wait, notify, notifyAll

3)      Understanding the motivation behind the above keyword and methods.

 

Assuming that step 1 has been done to some extent lets start with step3 and workout the details of step 2.

 

Simple loops

In order to understand Thread.sleep() lets first forget about it and do some simple loop programming.

Assume there is a thread of execution. This thread of execution could be

1)      simply the main method of a class

2)      it could be the run method of a Thread started from a main method,

3)      or it could be the run method of a Runnable started by a Thread instance from the main method

It matters little as long as we know that it is a thread of execution in each case.

Lets use the second choice namely – “the run method of a Thread started from a main method”

Lets put a simple loop in it.

 

Threads1.java

Output

started thread t

flag=true

flag=true

flag=true

……

Remark

Endlessly invoking doSomething() until the flag is set to false by say another thread t1

 

 

Lets modify this code slightly. (Lets not pay too much attention to Thread t1’s loop. Just concentrate on Thread t.)

Threads2.java

Output

started thread t

started thread t1

out of loop after 832370060 iterations.

Remark

Invoking doSomething() for a minimum of 5 seconds.

It is after 5 seconds that you see the line “out of loop”

 

 

Thread.sleep method

As long as what was inside doSomething() needs to be done as many times as is possible the above code is fine. Makes perfect sense.

 

But if however doSomething() was actually a doNothing and the really useful work was in afterLoop() we end up with a situation wherein inside thread t we are only waiting for the flag to become false so that thread t can go about doing its really important business of printing “out of loop after X iterations”.

 

In such a scenario there is something wrong with thread t. When it is inside the loop and doing nothing it is still hogging its share of the  CPU.

 

To rectify this we can make the following modification to above code.

 

Threads3.java

Output

started thread t

started thread t1

out of loop after 5 iterations.

Remark

The output is more or less the same.

 

The output is more or less the same but due to the sleep statement thread t actually releases the CPU for a period of 1 second during each iteration of the loop. (As a side effect the number of times it has been actually inside the loop is much less.) Because it has been hogging the CPU less, more of the CPU is available to all other threads.

 

Thus we see that the whole purpose of sleep statement is to give up the CPU for a specified time period. It can thus help make a doNothing loop or code more efficient.

 

The only reason your code was looping was because you did not know how long it would take for the boolean flag to become false. So you decided to check flag’s value after every 1 second. The choice of the time period depends on the actual time that would be taken for flag to become false. In this case actual time is 5 seconds so we chose a sleep time of 1 second. Choosing a very low sleep time would unnecessarily increase the number of loop iterations. Choosing a very large value for sleeptime say 10 seconds would mean less loop iterations but a much longer actual wait time than the actual 5 seconds.

 

Thread.yield

Even if the loop’s logic were really a do something worthwile and not a do nothing (as in the previous case) one should explore the option of invoking the static method Thread.yield() inside the doSomething loops. A thread can voluntarily yield the CPU without going to sleep or some other drastic means by calling the yield method. The yield method gives other threads of the same priority a chance to run. If there are no equal priority threads that are runnable, then the yield is ignored. If all your doSomething in a loop threads were also in the habit of yielding voluntarily they will depend less on the underlying OS for time-slicing.

 

YieldingThread.java

Output

started thread #1

started thread #2

#1 is doing something

#2 is doing something

#1 is doing something

#1 is doing something

#2 is doing something

Remark

 

 

 

Note: To share the CPU with a lower-priority thread, a thread can call Thread.sleep()

 

Thread.join (instance) method

 

A typical case of the above situation is when the only thing you were doing is

1)      Thread t is looping  and waiting for a flag to be set to false.

2)      The flag was set to false as the last line of code executed by some other thread t1.

In our example this was exactly the case. Often with some proper designing this can be made the case when the situation warrants the same.

With such typical cases rather than deal with the details of sleep time, total time and looping one can use a join.

 

Threads4.java

Output

Started thread t1

started thread t

thread t1 is about to die

after thread t1 completed now doing something

Remark

Done away with the loop.

 

A trivial variation of same code is

 

Threads5.java

Output

Started thread t1

started thread t

thread t1 is about to die

after thread t1 completed now doing something

Remark

Same code and output. Variation is in thread t1. t1 now uses sleep statement

 

Important note:

All the above code except join had nothing to do with any synchronization or locks. When invoking sleep, yield no locks are acquired or released. Therefore These sleep, yield, join are usually just means of improving a loop logic. That’s all. 

Also till now we have been seeing things from the perspective of code inside a thread.

For the rest of the discussion we change the perspective to that of thread inside code or rather thread inside code synchronized on a lock (whatever that is) object.

Join is a special case and its special-ness had better be discussed a little later. There is something special about the lock type and a consequence of the same speciality. But the programmer need not usually be bothered explicitly about this special lock and the synchronized keyword in his code  and should be only concerned with the overall effect details of which is discussed later.

 

The synchronized keyword

 

Suppose you have a private or protected int field named counter in a class. Suppose you have a method named incrementAndRead which does exactly that. Imagine that the object has been instantiated and multiple threads are invoking the method incrementAndRead concurrently on same object. It is possible that by the time the counter is being read (after an increment) by the first thread another thread again increments the counter. This is especially possible if there is a delay between the increment and the read. Instead of reading one increment you may end up reading multiple increments.

 

Threads6.java

Output

main thread of Threads6 is over

mismatch: counter being read here is not the value that was a result of one increment

Remark

Manifestation of problem being discussed

 

This problem can be addressed by making the method incrementAndRead synchronized

 

Threads7.java

Output

main thread of Threads7 is over

Remark

No mismatch because of synchronized keyword

 

A variation of the same code is

 

Threads8.java

Output

main thread of Threads8 is over

Remark

No mismatch because of synchronized keyword. The only difference is rather than synchronize the whole method only a code block is being synchronized. In more complex codes using this technique the code section being synchronized can be a subsection of the entire method’s code body. This may be preferred because synchronized statements slow down the code.

 

A further variation is

 

Threads9.java

Output

main thread of Threads9 is over

Remark

No mismatch because of synchronized keyword. The lock being used for synchronization here is not this but a separate object. In the previous two cases the lock being used was the instance of the Thread8/Thread7 “this” object.

 

So what’s going on? When a method (or a code block) is invoked by a thread we say that a thread has entered that method (or code block). When the method (or a code block) is not marked as synchronized multiple threads can concurrently enter that method (or a code block). In other words when the method (or a code block) is not marked as synchronized multiple threads can concurrently execute that method (or a code block).

 

When a synchronized method (or a synchronized code block) is invoked by a thread only one thread can enter that method (or code block). All other threads are blocked and must wait for the thread that is already inside the method (or code block) to finish.

 

The lock object:

So how does any thread know whether any previous thread is currently executing inside the synchronized method (or  synchronized code block)? This is where the lock comes into picture. Before entering the synchronized method (or  synchronized code block) a thread must first try to acquire the lock associated with the “synchronized” keyword.

 

1)      This lock may be the lock explicitly specified in the synchronized(Object lock) invocation for code blocks as in Thread9 and Thread8.

2)      For instance methods the lock is the “this” object as in Thread7.

3)      For static methods the lock is the object represented by the classname (an instance of java.lang.Class- the object you get when you do a getClass() on any instance if there is any instance for the class or what you get when you do a Class.forName(String className)).

 

When the previous thread comes out it is said to release the lock. A thread may release the lock because

1)      it is finished with the synchronized code or

2)      it temporarily stops executing (like in sleeping) in the synchronized code for some other reason.

When a thread releases the lock all the other threads compete to acquire the lock. We are not going into the details of why, when and which thread acquires the lock.

 

Note: Any object can be used as a synchronization lock. All objects have the following methods defined in java.lang.Object which are related to the lock functionality.

1)      wait()

2)      notify()

3)      notifyAll()

Refer to the API documentation for the variations.

 

Synchronized code categories

Code synchronized on a lock falls into 3 categories

  1. Simple synchronized code as in Thread7, Thread8 or Thread9
  2. Synchronized code that invokes the lock object’s notify() or notifyAll() method.
  3. Synchronized code that invokes the lock object’s wait() method.

Of these both categories 1 and 2 hold onto the synchronization lock as long as thread executing in the synchronized code comes out of the synchronized code one way or the other.

 

Wait and notify

In Category 3 the thread temporarily releases the lock (hoping to re-acquire it later) and the code synchronized on the lock stops executing because it encountered a wait operation on the lock object. So what do these threads wait for?

 

They wait for two things.

1)      A notification event which may happen when category 2 code runs its notify or notifyAll operation on the lock object.

2)      Plus they wait till the thread re-acquires the same synchronization lock that it gave up previously albeit temporarily. This means

a)      firstly the category2 code must finish executing all lines of its code. After all the notify operation or notifyAll operation the categaory 2 code executed on the lock may not necessarily be the last line of the code synchronized on the lock

b)      After that the lock is up for grabs.

c)      Catgory1-2-3 i.e. all three category code or threads that are trying to acquire the lock will now compete for the lock (because they each want to execute their synchronized code) but only one will get the lock. One by one all such threads may get their chance. This includes our threads that had executed the wait operation on the lock.

 

Note: It is possible based on the actual application code multiple threads may go into the wait state waiting on the same lock object. It is however not possible that two threads can concurrently go into the wait state on the same lock object. Why? Because both threads cannot concurrently enter code (same code or not) synchronized on the same synchronization lock object. They can do so only one at a time.

 

Actually: If any thread that was running code synchronized on a lock executed a wait operation on the lock then it releases the lock. It does not even try to re-acquire the lock thereafter. That thread simply pauses execution after the wait operation it executed on the lock. To enable this waiting thread to again compete with other threads for re-acquiring the same lock object a notification event must happen.

If there are 2 threads waiting on the same lock if some category2 thread were to execute a notify operation on the lock only one out of the two waiting threads will stop waiting because there was only one notification event. If you need all waiting threads to be notified execute notifyAll method on the lock.

A waiting thread that receives a notification does not wake up. All that the notification means is that this thread is no longer in the wait state. Before it starts actually running whoever is holding the lock must release the lock. If the thread is not able to re-acquire the lock immediately because the lock is not yet released or because some other thread grabbed the lock our formerly waiting thread moves from “waiting on lock state” to “blocked for lock acquisition state”. Both “waiting on lock state” and “blocked for lock acquistion state” are said to be  “non-runnable” states.

 

Join's special lock. Thread instance method join() invokes join(0).  Thread instance method join(long millis) and join(long millis, int nanos) are synchronized methods. The java documentation did not say so. But the sourcecode of join does say this. [Thanks to Ernest Friedman-Hill for pointing it out. I didn't know about this and learnt something here] The implication of this is that if two threads t1 and t2 are attempting to join on the same target thread t3 and one thread t1 has successfully entered the method or got the target thread t3 as lock then the other thread t2 will block on the lock t3 till t1 releases t3. From the programmer's perspective all it means is that both t1 and t2 are idling and not using up the CPU while they wait for t3 to complete its run method. As soon as t3 completes its run method t1 comes out of its join and starts running again. Its now that t2 can acquire t3 and go into join method but t3 is already done. So in effect both t1 and t2 are idling and not using up the CPU while they wait for t3 to get done during the join. You can even visualize the same as concurrent joins on same target thread. Only t2 restarts running just a little after t1. (In reality the second join code [on same target thread ] is blocked till the first join is done.). 

 

Sleep / Yield / Join vs Wait and Notify

Thread sleeping

Thread.yield

Thread joining

Wait and Notify

Method sleep() is a static method of the Thread class.

Method yield() is a static method of the Thread class.

Method join() is a instance method of the Thread class.

 

Methods wait(), notify() and notifyAll() are instance methods of the object class

Method  sleep() has nothing to do with synchronized code and the lock

Method  yield() has nothing to do with synchronized code and the lock

Other than the special lock discussed above method join() has nothing to do with synchronized code. Usually the developer's code does not have to explicitly be concerned about the synchronized code and the special lock

Methods wait(), notify() and notifyAll() cannot be invoked outside code synchronized on a lock object. In fact these methods can be invoked only on the lock object that is used for the thread synchronization.

The perspective usually is code inside a thread.

 

The perspective is that of threads inside synchronized code.

Used mostly to improve simple doNothing loops that are waiting for some boolean condition to become true or false.

Allows other threads of even lower priority to share the CPU

Used mostly to improve simple doSomething loops. The basic idea being that even if the underlying OS does not time-slice, these threads because of the yield() methods will try not to hog the CPU by yielding to other threads having same priority

Used mostly to improve simple doNothing loops that are waiting for some other thread to get done. Improves upon the sleep() in a loop technique. Allows other threads of even lower priority to share the CPU

Is a more complex beast.

 

Theoretically if in a high-priority thread sleep(), yield(), wait() is not invoked and the thread also does not block on any I/O, all other lower priority threads (and even other threads with same priority) may not be able to run till the higher priority thread is done.  If this is an issue you should take care of this by employing sleep, yield and wait suitably. We should not assume that time-slicing will occur in all OSs.

Deadlock

No discussion on thread synchronization can be completed without discussing the synchronization related deadlock possibility as illustrated in this example. Deadlocks should be avoided in general.

Deadlock.java

Output

t1 aqquired o1

t2 aqquired o2

t1 out of loop1 and blocking for o2 lock acquisition

t2 out of loop2 and blocking for o1 lock acquisition

Remark

Here1 or Here2 is never printed.

The simple loops used in the code can be improved. You know how.

 

Note: As long as a thread has entered a  synchronized method (or a synchronized code block) the synchronized method (or the synchronized code block) can freely invoke other methods synchronized on same lock for the same thread. Otherwise a single thread would deadlock itself on a lock that it already holds.

 

InterruptedException

Some of the methods mentioned above namely – Thread.sleep(), Thread.join(), Object.wait() were declared to throw java.lang.InterruptedException. “Thrown when a thread is waiting, sleeping, or otherwise paused for a long time and another thread interrupts it using the interrupt method in class Thread.” In general unless some thread is actually invoking the interrupt() instance method of Thread objects there is no need to handle this exception. What I mean is that the programmer has to of course got to catch it because it is a checked exception but he need not do anything inside the catch block unless another thread does interrupt.

 

 

 

Lastly: This really is  a basics only code and concept centric write-up which just seeks to elucidate the preliminaries of thread programming. Please follow this up with further study. Specifically we have discussed only one variation of each method.

 

Author: Raghu