<img height="1" width="1" style="display:none" src="https://www.facebook.com/tr?id=160269078105920&amp;ev=PageView&amp;noscript=1">

Simple Solutions for Java threads Problems

Wednesday 19 of June, 2019.
Reading Time: 5 minutes.
By Jonnathan Bartly



“With great power comes great ...”

Let's start without delay! Threads, what is a thread?

A thread is the route that is followed when executing a program. All Java programs have at least one thread, known as the main thread, which is created by the Java Virtual Machine. A thread is a single sequential control flow within a program.

A single thread also has a beginning, a sequence and an ending. At any given moment during the execution time of the subprocess, there is a single execution point. However, a thread in itself is not a program and can not be executed on its own. Rather, it runs within a program and takes advantage of the resources allocated for that program and its environment.

The true emotion that surrounds the threads is not about a single sequential thread. It involves the use of several subprocesses that run at the same time to perform different tasks, such as managing large amounts of data or operations that require long execution times that can slow down the application and prevent a good user experience.

Some advantages

  1. Reduce development time
  2. Improves the performance of complex operations.
  3. Parallel tasks.

Disadvantages

  1. Multiple threads can interfere with each other when sharing hardware resources.
  2. It can cause deadlocks, starvation and race problems if they are not used correctly.

Deadlock Problem

A Deadlock problem describes a situation in which two or more threads are blocked forever, waiting for one another. The deadlock occurs when several subprocesses need the same object but they get it in a different order. Let's see the code!


public class DeadLockThread {

   public static Object object1 = new Object();
   public static Object object2 = new Object();
   
   public static void main(String args[]) {
      Thread1 thread1 = new Thread1();
      Thread2 thread2 = new Thread2();
      thread1.start();
      thread2.start();
   }
   
   private static class Thread1 extends Thread {
      public void run() {
         synchronized (object1) {
            System.out.println("Thread 1: Holds object 1.");
            
            try { 
               Thread.sleep(10); 
            } catch (Exception e) {}
            System.out.println("Thread 1: Waiting for object 2.");
            
            synchronized (object2) {
               System.out.println("Thread 1: Holds object 1 and object 2.");
            }
         }
      }
   }

   private static class Thread2 extends Thread {
      public void run() {
         synchronized (object2) {
            System.out.println("Thread 2: Holds object 2.");
            
            try { 
               Thread.sleep(10); 
            } catch (Exception e) {}
            System.out.println("Thread 2: Waiting for object 1.");
            
            synchronized (object1) {
               System.out.println("Thread 2: Holds object 1 and object 2.");
            }
         }
      }
   } 
}

The previous code will give us an output similar to this one:


Thread 1: Holds object 1.
Thread 2: Holds object 2.
Thread 1: Waiting for object 2.
Thread 2: Waiting for object 1.
 

And that's because the first thread is using the objet1 and waiting for the objet2, but the second thread is using the objet2 and will not release it until it has also used the objet1. This is a clear case of threads blocked forever.

Simple solution for the Deadlock problem

 

Changing the order of objects prevents the program from entering a deadlock situation. Let's see the code:


public class DeadLockThreadSolution {

   public static Object object1 = new Object();
   public static Object object2 = new Object();
   
   public static void main(String args[]) {
      Thread1 thread1 = new Thread1();
      Thread2 thread2 = new Thread2();
      thread1.start();
      thread2.start();
   }
   
   private static class Thread1 extends Thread {
      public void run() {
         synchronized (object1) {
            System.out.println("Thread 1: Holds object 1.");
            
            try {
               Thread.sleep(10);
            } catch (Exception e) {}
            System.out.println("Thread 1: Waiting for object 2.");
            
            synchronized (object2) {
               System.out.println("Thread 1: Holds object 1 and object 2.");
            }
         }
      }
   }
   private static class Thread2 extends Thread {
      public void run() {
         synchronized (object1) {
            System.out.println("Thread 2: Holds object 1.");
           
            try {
               Thread.sleep(10);
            } catch (Exception e) {}
            System.out.println("Thread 2: Waiting for object 2.");
            
            synchronized (object2) {
               System.out.println("Thread 2: Holds object 1 and object 2.");
            }
         }
      }
   } 
}

The output will be similar to this:


Thread 1: Holds object 1.
Thread 1: Waiting for object 2.
Thread 1: Holds object 1 and object 2.
Thread 2: Holds object 2.
Thread 2: Waiting for object 1.
Thread 2: Holds object 1 and object 2.

Starvation Problem

The Starvation problem occurs when a thread is continuously denied access to resources and as a result, can not move forward. This usually happens when greedy threads consume shared resources for long periods of time. Let's review the code:


public class StarvationThread {

  	private static Object object = new Object();
  	private static volatile boolean isActive = true;
  
  	public static void main(String[] args) {

    	Thread thread1 = new Thread(new Resource(), "Thread 1 - Priority 10");
    	Thread thread2 = new Thread(new Resource(), "Thread 2 - Priority 8");
    	Thread thread3 = new Thread(new Resource(), "Thread 3 - Priority 5");
    	Thread thread4 = new Thread(new Resource(), "Thread 4 - Priority 3");
    	Thread thread5 = new Thread(new Resource(), "Thread 5 - Priority 1");
    
    	thread1.setPriority(10);
    	thread2.setPriority(8);
    	thread3.setPriority(5);
    	thread4.setPriority(3);
    	thread5.setPriority(1);
    
    	thread1.start();
    	thread2.start();
    	thread3.start();   
    	thread4.start();   
    	thread5.start();   

    	try {
      		Thread.sleep(5000);
    	} catch (Exception e) {}

    	isActive = false;
  	}
  
  	private static class Resource implements Runnable {

    	private AtomicInteger resourceUsed = new AtomicInteger();
    
    	public void run() {
      		while (isActive) {
        		synchronized (object) {
    				System.out.format("%s: Resource has be used %d times.\n", Thread.currentThread().getName(), resourceUsed.getAndIncrement());
        		}
      		}

      		System.out.format("\n->%s: Resource was used %d times.\n\n", Thread.currentThread().getName(), resourceUsed.get());
    	}
  	}
}

 

The previous code will give us an output similar to this one:


Thread 1 - Priority 10: Resource was used 40652 times.
Thread 2 - Priority 8: Resource was used 30407 times.
Thread 3 - Priority 5: Resource was used 64029 times.
Thread 4 - Priority 3: Resource was used 10014 times.
Thread 5 - Priority 1: Resource was used 546 times.
 

As you can see, thread # 5 has been denied access to resources and due to this, the progress made is minimal.

Simple Solution to Starvation problem

In general, it is recommended not to modify the priorities of the threads, since this is the main cause of the Starvation problem. Once you start modifying your application with thread priorities, it closely matches the specific platform and also presents the risk that the threads do not have access to resources.

Race Problem

The Race problem occurs when a subprocess is going to act according to a verification condition, but another subprocess may have interleaved and changed the value of the field. Now, the first thread will act based on a value that is no longer valid. Let's review the code:



public class RaceThread {

    private int number;
     
    public static void main(String[] args) {

        final RaceThread raceThread = new RaceThread();
         
        for (int i = 1; i < 100; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    raceThread.changeNumber();
                }
            }, "Thread " + i).start();
        }
    }

    public void changeNumber() {
        if (number == 0) {
            number = 1;
            System.out.println(Thread.currentThread().getName() + " - changed the number.");
        } else {
            System.out.println(Thread.currentThread().getName() + " - did Not change the number.");
        }
    }     
}

The result will be something similar to this:


Thread 13 - changed the number.
Thread 17 - changed the number.
Thread 35 - did Not change the number.
Thread 10 - changed the number.
Thread 48 - did Not change the number.
Thread 14 - changed the number.
Thread 60 - did Not change the number.
Thread 6 - changed the number.
Thread 5 - changed the number.
Thread 63 - did Not change the number.
Thread 18 - did Not change the number.

Some of the threads changed the value of number and other threads did not change it. That is the reason why the use number for a condition is not valid in this code.

Simple Solution to Race Problem

A simple way to correct this is to use synchronization. Synchronization ensures that only one thread can access the resource at any given time. Let's see the code:

 


public class RaceThreadSolution {

    private int number;
     
    public static void main(String[] args) {

        final RaceThread raceThread = new RaceThread();
         
        for (int i = 1; i < 100; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    raceThread.changeNumber();
                }
            }, "Thread " + i).start();
        }
    }

    public synchronized void changeNumber() {
        if (number == 0) {
            number = 1;
            System.out.println(Thread.currentThread().getName() + " - changed the number.");
        } else {
            System.out.println(Thread.currentThread().getName() + " - did Not change the number.");
        }
    }
     
}

In this case, only the first thread changed the value of number and kept it valid for use in a condition.


T1 - changed the number.
T54 - did Not change the number.
T53 - did Not change the number.
T62 - did Not change the number.
T52 - did Not change the number.
T71 - did Not change the number.

Conclusion

There is always a solution to every problem. You just need to take a moment, review your code and analyze it for whatever your ultimate goal is. The threads are a powerful tool that can make our lives as super easy engineers, but remember that with great power comes a great responsibility!

About Avantica

At Avantica we work as a software partner that helps you meet your business objectives and solve every challenge that comes your way. We offer dedicated teams and constantly seek the best methodologies to provide you with the best results.

Let's Start a Project Together

 

 

 

PREVIOUS
Preparing for an Interview: Guidelines & Tips
NEXT
How to Manage the Android Main Thread: Coroutines