More About Multithreading in Java

You are reading the second part of the multithreading in Java series. The first part covered the basics of threads, explained the theory that lies behind them, and then gave examples of the two possible ways of creating new threads. This article resumes the journey by getting even deeper into concepts like the ThreadGroup class, synchronization, and inter-thread communication.

Before we begin, it’s worth noting that this article is a sequel, so it presumes sound knowledge of the things we learned in the prior part. That part of the series was also published here, so should you have any doubts, please refer back to it. 

There are three kinds of variable types to which threads have access. The first type is local variables, but these are private, meaning that the threads cannot access each other’s variables; plus, if more threads are running the same snippet, then each thread has its own copies of the local variables. Then we have object attributes and static attributes. The latter are reachable by all threads. Object attributes can be shared (explicitly), if necessary. 

Since we can work with multiple threads, it makes sense that Java allows more than one thread to be grouped. When we are working with a huge number of threads, this actually simplifies our job, since we can manage threads by their group. Say we’re juggling five threads, and they’re all part of a ThreadGroup. It’s so much easier to stop them by calling the method via the group, rather than doing so independently. 

Moreover, we can hierarchically add groups of threads into one thread group. This is how the entire concept turns into a tree-like hierarchy. It should be noted that threads can only access information from their own ThreadGroup, but not their parent’s or other’s. We can work with Threads just like we would with arrays or sets.  

Let’s see the following example:

 

class MyThread extends Thread{

public MyThread (String name){

super(name);

}

 

public void run(){

try{

for(int i = 0; i < 10; ++i)

sleep(1000);

}

catch(InterruptedException e){}

}

}

 

class MyThreadGroup{

public static void main (String args[]){

MyThread t[] = new MyThread[10];

for (int i = 0; i < 10; ++i)

t[i] = new MyThread("Thread no: " + i);

for (int i = 0; i < 10; i+=2)

t[i].start();

ThreadGroup grp = Thread.currentThread().getThreadGroup();

int instanceCount = grp.activeCount();

Thread th[] = new Thread[instanceCount];

int activeCount = grp.enumerate(th);

System.out.println("Thread Group Name: " + grp.getName());

System.out.println("Count of Threads: " + instanceCount);

System.out.println("Count of Active Threads: " + activeCount);

System.out.println("List of Active Threads: ");

for (int i = 0; i < activeCount; ++i)

System.out.println(th[i].getName());

grp.interrupt();

}

}

 

And guess what, the above code snippet has the following output:

 

Thread Group name: main

Count of Threads: 6

Count of Active Threads: 6

List of Active Threads:

main

Thread no: 0

Thread no: 2

Thread no: 4

Thread no: 6

Thread no: 8

 Now it’s time for us to continue with some advanced ideas. 

{mospagebreak title=Stopping and Synchronization} 

We covered earlier that threads must be started with the start() method. Failing to do so and then running them via run() results in a sequential execution and not what we want, which is multi-threading: simultaneous execution of multiple threads. All right but one might also ask, how do we stop a thread that is running? 

There’s a method called stop() that does that—however, its use isn’t advised. That is because there’s a chance it might hog the execution, dragging the system into a stale mate or dead point. The stop() method does not free up allocated system resources. Fortunately, there is a simple and effective workaround. We’re going to introduce a boolean variable (attribute) that always refers to the state of the thread. 

Let’s call it isRunning. We declare this as an attribute of the class with the following modifiers: private volatile. At first, surely, this variable starts out as true. However, we will write a method that sets this to false, such as the stopping() thread. Right after that, we can easily modify the run() method so that it runs while isRunning is true.

 

class MyThread extends Thread{

private volatile boolean isRunning = true;

 

public void stopping(){

isRunning = false;

}

 

public void run(){

while (isRunning){

// action goes here

try{

sleep(10);

}

catch (InterruptedException e){}

}

}

Based on the above source code, threads can be stopped by calling the stopping() method instead of the inherited stop(), which might cause problems. This is much safer. 

And finally, the time has come for us to talk about synchronization. It is one of the most important concepts when working with multiple threads within an application. In order to fully grasp the concept of synchronized execution, we’re required to also talk about unsynchronized (asynchronous) execution. 

If we don’t explicitly “synchronize” the execution of threads, then by all norms they’re executed asynchronously. This kind of multi-threading isn’t reliable because it does not give consistent results. A so-called “race condition” appears, which means the threads are “racing each other” because some operations can be completed faster than others, the processor might have faster access to some data (registers), and so forth. 

Moreover, this also means that executing the same application another time does not yield the same results with a 100% guarantee. By definition, a thread becomes synchronized if you become the owner of its object monitor. This is somewhat similar to locking the object. And you can do this in two possible ways. 

Each time the program enters a synchronized method, it becomes owner of that object’s monitor (or class monitor in the case of static methods). In a nutshell, this locks the thread’s access to the synchronized method if another thread is already executing it. And this blockage applies until the lock is released by exiting the method or invoking the wait() method. The latter wait() temporarily releases the monitor. 

You can make a method synchronized by adding the “synchronized” keyword prior to its declaration. This is the first solution. Another way to make a thread become owner of its object monitor is by using the “synchronized” statement on a common object. The latter is safer because it always releases the lock once the execution is done.

Consider the following example for the synchronized method path.

 

synchronized void func(…){

}

 

And now let’s see another example for the synchronized common object solution.

 

void func(…){

// …

synchronized(obj){

// …

}

}

 

On the next page we’ll tackle inter-thread communication and present an example of the popular producer-consumer model. That problem pops up very frequently as an approach used by professors to explain how inter-thread communication is applied. 

{mospagebreak title=Inter-Thread Synchronization and Communication} 

Inter-thread communication is all about making synchronized threads communicate with each other. Sometimes one might want to pause (or delay) the execution of a thread until some condition is met. And this condition only occurs if another thread does such and such. To implement these, inter-thread communication is necessary.

Java offers three possible solutions. The following three methods are inherited from the Object class: public final void wait(), public final void notify(), public final void notifyAll(). The first method forces a thread to wait until some other thread calls either notify() or notifyAll() methods. The second notify() method wakes up the thread that invoked the wait() method on the same object. And the third method, notifyAll(), wakes up all of the threads that called the wait() method on the same object. 

As mentioned earlier, wait() is defined in the Object class, and it can only be called from a synchronized thread. It may throw an InterruptedException. In short, it releases the lock it is holding on an object’s monitor, thereby allowing other threads to run. The notify() method wakes up any of the threads that are waiting to release the object’s monitor lock. This choice is arbitrary and at the discretion of the JVM. 

The awakened threads will resume execution right from where they were paused. The same applies to notifyAll(). Now let’s explain the producer-consumer model. Imagine the shelf of a bookstore, for example. The producer places books on the shelf, while the consumer grabs them. There are two conditions necessary. First, there should be books on the shelf for the consumer to grab. And second, if there are too many books on the shelf, then there isn’t any space left for more books. 

The producer and consumer model consists basically of two synchronized threads that should communicate with each other. It’s not that hard at all, but if it is neglected, the results can be disastrous. So this problem is going to implement two of the major concepts we have learned in this article: thread synchronization and inter-thread communication. All right, so let’s begin writing the code!

 

public class Books{

private int count;

private boolean isReady = false;

 

public synchronized int grab(){

while (isReady == false){

try{

wait();

}

catch (InterruptedException e){}

}

isReady = false;

notifyAll();

return count;

}

 

public synchronized void place(int newCount){

while (isReady == true){

try{

wait();

}

catch (InterruptedException e){}

count = newCount;

isReady = true;

notifyAll();

}

}

}

 

And now the implementation of the Producer class:

 

public class Producer extends Thread{

private Books myShelf;

private int number;

 

public Producer(Books b, int number){

myShelf = b;

this.number = number;

}

 

public void run(){

for (int i = 0; i < 5; ++i){

myShelf.place(i);

System.out.println("Prod No#: " + this.number + " places " +i);

try{

sleep(10);

}

catch (InterruptedException e){}

}

}

}

 

Finally, here comes the Consumer class as well. Check it out below.

 

public class Consumer extends Thread{

private Books myShelf;

private int number;

 

public Consumer(Books b, int number){

myShelf = b;

this.number = number;

}

 

public void run(){

int val = 0;

for (int i = 0; i < 5; ++i){

val = myShelf.grab();

System.out.println("Cons No#: " + this.number + " grabs " +val);

}

}

}

 

All that is left is creating a class with public static void main, so that we can launch this application. The last snippet is below.

 

public class MyClass{

public static void main (String args[]){

Books b = new Books();

Producer p = new Producer (b, 1);

Consumer c = new Consumer (b, 1);

 

p.start();

c.start();

}

}

 

And finally, here’s the attached output. Check it out. And we’re done for now!

 

Prod No#: 1 places 0

Cons No#: 1 grabs 0

Prod No#: 1 places 1

Cons No#: 1 grabs 1

Prod No#: 1 places 2

Cons No#: 1 grabs 2

Prod No#: 1 places 3

Cons No#: 1 grabs 3

Prod No#: 1 places 4

Cons No#: 1 grabs 4

{mospagebreak title=Taking a Break} 

As you can see, we have arrived at the end of this sequel article. By now you should know how to work with multiple threads, create them, make them share data and variables between each other, synchronize attributes, manage their states, and make them communicate between each other. 

Before we finish, there are two more interesting types of classes regarding threads that we should mention. These are the Timer Class and the TimerTask Class. With the help of these we can schedule some parts of our program. Basically, the scheduled task will be run as a separate thread (like a background thread) when its appropriate time arrives. As a rule of thumb, these timer tasks should be quick, otherwise they might hog and/or speed up (since the task was delayed) the application execution. 

Here’s a really quick example; please complete the snippet with the required parts.

 

class Scheduler{

Timer timer;

 

public Scheduler(int seconds){

timer = new Timer();

timer.schedule(new RemindTask(), seconds*1000);

}

 

class RemindTask extends TimerTask{

public void run(){

System.out.println(" Boo! "); // this is the actual task

timer.cancel();

}

}

Thanks for following this series; hopefully, you’ve found it educational. We welcome and appreciate all kinds of feedback in the blog comment section below; thus, feel free to write your opinion, ask questions, and we’ll clarify your dilemmas. 

In closing, I’d like to invite you to join our experienced community of technology professionals on all areas of IT&C starting from software and hardware up to consumer electronics at Dev Hardware Forums. Also, be sure to check out the community of our sister site at Dev Shed Forums. We are friendly and we’ll do our best to help you.

[gp-comments width="770" linklove="off" ]

antalya escort bayan antalya escort bayan Antalya escort diyarbakir escort