Threads and Multithreading – Part 2

Threads - images of multicoloured threads

In the last post, we looked at the idea of threads and multi-threading in Java, and how to create code that could be run as a thread in the background.

In this post, we’ll look at the lifecycle of a thread. When we control threads manually, we have to be aware of the various states that a thread can be in, and how to transition between states.

Lifecycle of Threads

Threads can be in three states: blocked, dead and runnable/running. “Runnable” in this context does not refer to the Runnable interface, but to the state of the thread itself.

The following diagram shows the different runtime states of a thread and what events cause transitions between states.

Diagram showing the lifecycle of threads in Java

Starting a Thread

We can create a new Java thread object using the new keyword. This only creates a Java object in memory; it does not create a low-level operating system thread. This is only done when the start() method of the thread is called.

After the start() method is called, the actual low-level thread is spawned and becomes runnable. A runnable thread is not necessarily running: a runnable thread only runs when the operating system provides the necessary resources.

A running thread will not necessarily keep running continuously. The operating system, through its scheduler, controls the allocation of processing time on whatever basis it uses: pre-emptive, time slicing or co-operative.

Every thread has a name so that it can be identified. We can use the same name for multiple threads if we’d like. If we don’t specify a name in the thread constructor, a new name will automatically be generated.

Blocked and Dead Threads

A thread can become blocked, i.e. it no longer receives resources and is therefore no longer runnable. It can become blocked for a number of reasons:

  • The thread is waiting on I/O that is blocked.

  • The sleep() method has been called, with a number of milliseconds specified for the sleep period.

  • The suspend() method has been called. The suspend() method is deprecated and should not be used because it is unsafe. It can lead to deadlock where the suspended thread holds an object lock that another thread needs to be able to resume the suspended thread.

  • It is waiting for an object lock/monitor (we’ll look at this in a later post).

A thread becomes unblocked when:

  • The I/O for which it was waiting becomes free.

  • The time allocated to the sleep() method has elapsed.

  • The resume() method has been called on a suspended thread. The resume() has been deprecated for the same reasons as the suspend() method.

  • The notify() or notifyAll() method has advised the waiting thread(s) to retry the monitor.

A thread can die for two reasons:

  • It dies naturally when its run() method exits normally.

  • It dies abruptly either when an uncaught exception terminates the run() method, or if the stop() method is called.

When the stop() method is called, the thread is forced to stop whatever it is doing abnormally, and throws a newly created ThreadDeath error object. The stop() method is deprecated and should not be used because it is unsafe: it can leave the data on which the thread was operating in an inconsistent state. Most uses of stop() should be replaced by code that modifies some variable to indicate that the target thread must stop running.

Thread Priority

Threads are allocated the same priority as their parent thread, i.e. the main thread that created them. We can change this to a different priority, either before the thread starts running or during its execution by using the setPriority() method.

The setPriority() method takes an int parameter with values ranging from 1 to 10. The Thread class provides the constants Thread.MIN_PRIORITY with the value of 1, Thread.NORM_PRIORITY with the value of 5 and Thread.MAX_PRIORITY with the value of 10. Keep in mind, however, that not all operating systems have 10 levels of priority – some have more, some have less.

Joining a Thread

After the main program has started any threads, it can continue its processing in parallel with these threads. If it needs to know that a particular thread has completed its work, it must call the join() method on that thread.

At this point, the main program will go into a blocked state until the joined thread has died. When the thread has died, the main program will become unblocked and be able to proceed and exit if necessary. There are two join() methods: one will wait indefinitely; the other has a timeout after which the main program will proceed regardless.

Yielding Control

A well-behaved thread executing a compute-intensive method should call the yield() method occasionally. The yield() method moves the running thread to the back of the runnable queue, and gives other equal priority threads a chance to run. We should call the yield() method in case the operating system uses a cooperative approach, because all other threads may then starve by not getting enough machine cycles. This should not be required in the case of a pre-emptive or time-sliced operating system.

The yield() method is not guaranteed to work, as final control depends on the thread scheduler of the specific operating system. A better solution would be to call the Thread.sleep() method occasionally within a compute-intensive thread.

Conclusion

A bit more of the iceberg of threads has been exposed, although there’s still a lot more to come!

In the next few posts we’ll look at how to control threads, how to interrupt them and handle them being interrupted. We’ll also investigate the use of the synchronized keyword, and the wait() and notify() methods of the Object class. Then we’ll look at the exciting classes in the java.util.concurrent package which make creating, running and controlling threads vastly easier.

Please share your comments.

Until then, stay safe and keep coding!

Leave a Comment

Your email address will not be published. Required fields are marked *

Code like a Java Guru!

Thank You

We're Excited!

Thank you for completing the form. We're excited that you have chosen to contact us about training. We will process the information as soon as we can, and we will do our best to contact you within 1 working day. (Please note that our offices are closed over weekends and public holidays.)

Don't Worry

Our privacy policy ensures your data is safe: Incus Data does not sell or otherwise distribute email addresses. We will not divulge your personal information to anyone unless specifically authorised by you.

If you need any further information, please contact us on tel: (27) 12-666-2020 or email info@incusdata.com

How can we help you?

Let us contact you about your training requirements. Just fill in a few details, and we’ll get right back to you.

Your Java tip is on its way!

Check that incusdata.com is an approved sender, so that your Java tips don’t land up in the spam folder.

Our privacy policy means your data is safe. You can unsubscribe from these tips at any time.