Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon

Java Multithreading: How to synchronize threads to implement critical sections and avoid race conditions

Save for later
  • 13 min read
  • 30 May 2018

article-image

One of the most common situations in concurrent programming occurs when more than one execution thread shares a resource. In a concurrent application, it is normal for multiple threads to read or write the same data structure or have access to the same file or database connection. These shared resources can provoke error situations or data inconsistency, and we have to implement some mechanism to avoid these errors. These situations are called race conditions and they occur when different threads have access to the same shared resource at the same time.

Therefore, the final result depends on the order of the execution of threads, and most of the time, it is incorrect. You can also have problems with change visibility. So if a thread changes the value of a shared variable, the changes would only be written in the local cache of that thread; other threads will not have access to the change (they will only be able to see the old value).

We present to you a java multithreading tutorial taken from the book, Java 9 Concurrency Cookbook - Second Edition, written by Javier Fernández González.


The solution to these problems lies in the concept of a critical section. A critical section is a block of code that accesses a shared resource and can't be executed by more than one thread at the same time. To help programmers implement critical sections, Java (and almost all programming languages) offers synchronization mechanisms. When a thread wants access to a critical section, it uses one of these synchronization mechanisms to find out whether there is any other thread executing the critical section.

If not, the thread enters the critical section. If yes, the thread is suspended by the synchronization mechanism until the thread that is currently executing the critical section ends it. When more than one thread is waiting for a thread to finish the execution of a critical section, JVM chooses one of them and the rest wait for their turn.

Java language offers two basic synchronization mechanisms:

  • The  synchronized keyword
  • The  Lock interface and its implementations


In this article, we explore the use of synchronized keyword method to perform synchronization mechanism in Java.

So let's get started:

Synchronizing a method


In this recipe, you will learn how to use one of the most basic methods of synchronization in Java, that is, the use of the synchronized keyword to control concurrent access to a method or a block of code. All the synchronized sentences (used on methods or blocks of code) use an object reference. Only one thread can execute a method or block of code protected by the same object reference.

When you use the synchronized keyword with a method, the object reference is implicit. When you use the synchronized keyword in one or more methods of an object, only one execution thread will have access to all these methods. If another thread tries to access any method declared with the synchronized keyword of the same object, it will be suspended until the first thread finishes the execution of the method.

In other words, every method declared with the synchronized keyword is a critical section, and Java only allows the execution of one of the critical sections of an object at a time. In this case, the object reference used is the own object, represented by the this keyword. Static methods have a different behavior. Only one execution thread will have access to one of the static methods declared with the synchronized keyword, but a different thread can access other non-static methods of an object of that class.

You have to be very careful with this point because two threads can access two different synchronized methods if one is static and the other is not. If both methods change the same data, you can have data inconsistency errors. In this case, the object reference used is the class object.

When you use the synchronized keyword to protect a block of code, you must pass an object reference as a parameter. Normally, you will use the this keyword to reference the object that executes the method, but you can use other object references as well. Normally, these objects will be created exclusively for this purpose. You should keep the objects used for synchronization private.

For example, if you have two independent attributes in a class shared by multiple threads, you must synchronize access to each variable; however, it wouldn't be a problem if one thread is accessing one of the attributes and the other accessing a different attribute at the same time. Take into account that if you use the own object (represented by the this keyword), you might interfere with other synchronized code (as mentioned before, the this object is used to synchronize the methods marked with the synchronized keyword).

In this recipe, you will learn how to use the synchronized keyword to implement an application simulating a parking area, with sensors that detect the following: when a car or a motorcycle enters or goes out of the parking area, an object to store the statistics of the vehicles being parked, and a mechanism to control cash flow. We will implement two versions: one without any synchronization mechanisms, where we will see how we obtain incorrect results, and one that works correctly as it uses the two variants of the synchronized keyword.

The example of this recipe has been implemented using the Eclipse IDE. If you use Eclipse or a different IDE, such as NetBeans, open it and create a new Java project.

How to do it...


Follow these steps to implement the example:

  1. First, create the application without using any synchronization mechanism. Create a class named ParkingCash with an internal constant and an attribute to store the total amount of money earned by providing this parking service:

public class ParkingCash { 
          private static final int cost=2; 
          private long cash;
public ParkingCash() { 
cash=0; 
}

  1. Implement a method named vehiclePay() that will be called when a vehicle (a car or motorcycle) leaves the parking area. It will increase the cash attribute:

        public void vehiclePay() { 
          cash+=cost; 
        }

  1. Finally, implement a method named close() that will write the value of the cash attribute in the console and reinitialize it to zero:

          public void close() { 
            System.out.printf("Closing accounting"); 
            long totalAmmount; 
            totalAmmount=cash; 
            cash=0; 
            System.out.printf("The total amount is : %d",
                              totalAmmount); 
          } 
        }

  1. Create a class named ParkingStats with three private attributes and the constructor that will initialize them:

public class ParkingStats { 
          private long numberCars; 
          private long numberMotorcycles; 
          private ParkingCash cash;
public ParkingStats(ParkingCash cash) { 
numberCars = 0; 
numberMotorcycles = 0; 
this.cash = cash; 
}

  1. Then, implement the methods that will be executed when a car or motorcycle enters or leaves the parking area. When a vehicle leaves the parking area, cash should be incremented:

public void carComeIn() { 
          numberCars++; 
        }
public void carGoOut() { 
numberCars--; 
cash.vehiclePay(); 
}

public void motoComeIn() { 
          numberMotorcycles++; 
        }
public void motoGoOut() { 
numberMotorcycles--; 
cash.vehiclePay(); 
}

  1. Finally, implement two methods to obtain the number of cars and motorcycles in the parking area, respectively.
  2. Create a class named Sensor that will simulate the movement of vehicles in the parking area. It implements the Runnable interface and has a ParkingStats attribute, which will be initialized in the constructor:

public class Sensor implements Runnable {
private ParkingStats stats;

public Sensor(ParkingStats stats) { 
this.stats = stats; 
}

  1. Implement the run() method. In this method, simulate that two cars and a motorcycle arrive in and then leave the parking area. Every sensor will perform this action 10 times:

        @Override 
        public void run() { 
          for (int i = 0; i< 10; i++) { 
            stats.carComeIn(); 
            stats.carComeIn(); 
            try { 
              TimeUnit.MILLISECONDS.sleep(50); 
            } catch (InterruptedException e) { 
              e.printStackTrace(); 
            } 
            stats.motoComeIn(); 
            try { 
              TimeUnit.MILLISECONDS.sleep(50); 
            } catch (InterruptedException e) { 
              e.printStackTrace(); 
            }

            stats.motoGoOut(); 
            stats.carGoOut(); 
            stats.carGoOut(); 
          } 
        }

  1. Finally, implement the main method. Create a class named Main with the main() method. It needs ParkingCash and ParkingStats objects to manage parking:

public class Main {
public static void main(String[] args) {

ParkingCash cash = new ParkingCash(); 
ParkingStats stats = new ParkingStats(cash);

System.out.printf("Parking Simulatorn");

  1. Then, create the Sensor tasks. Use the availableProcessors() method (that returns the number of available processors to the JVM, which normally is equal to the number of cores in the processor) to calculate the number of sensors our parking area will have. Create the corresponding Thread objects and store them in an array:

        intnumberSensors=2 * Runtime.getRuntime()
                                           .availableProcessors(); 
        Thread threads[]=new Thread[numberSensors]; 
        for (int i = 0; i<numberSensors; i++) { 
          Sensor sensor=new Sensor(stats); 
          Thread thread=new Thread(sensor); 
          thread.start(); 
          threads[i]=thread; 
        }

Unlock access to the largest independent learning library in Tech for FREE!
Get unlimited access to 7500+ expert-authored eBooks and video courses covering every tech area you can think of.
Renews at €18.99/month. Cancel anytime
  1. Then wait for the finalization of the threads using the join() method:

        for (int i=0; i<numberSensors; i++) { 
          try { 
            threads[i].join(); 
          } catch (InterruptedException e) { 
            e.printStackTrace(); 
          } 
        }

  1. Finally, write the statistics of Parking:

            System.out.printf("Number of cars: %dn",
                              stats.getNumberCars()); 
            System.out.printf("Number of motorcycles: %dn",
                               stats.getNumberMotorcycles()); 
            cash.close(); 
          } 
        }


In our case, we executed the example in a four-core processor, so we will have eight Sensor tasks. Each task performs 10 iterations, and in each iteration, three vehicles enter the parking area and the same three vehicles go out. Therefore, each Sensor task will simulate 30 vehicles.

If everything goes well, the final stats will show the following:

  • There are no cars in the parking area, which means that all the vehicles that came into the parking area have moved out
  • Eight Sensor tasks were executed, where each task simulated 30 vehicles and each vehicle was charged 2 dollars each; therefore, the total amount of cash earned was 480 dollars


When you execute this example, each time you will obtain different results, and most of them will be incorrect. The following screenshot shows an example:

java-multithreading-synchronize-threads-implement-critical-sections-img-0

We had race conditions, and the different shared variables accessed by all the threads gave incorrect results. Let's modify the previous code using the synchronized keyword to solve these problems:

  1. First, add the synchronized keyword to the vehiclePay() method of the ParkingCash class:

        public synchronized void vehiclePay() { 
          cash+=cost; 
        }

  1. Then, add a synchronized block of code using the this keyword to the close() method:

        public void close() { 
          System.out.printf("Closing accounting"); 
          long totalAmmount; 
          synchronized (this) { 
            totalAmmount=cash; 
            cash=0; 
          } 
          System.out.printf("The total amount is : %d",totalAmmount); 
        }

  1. Now add two new attributes to the ParkingStats class and initialize them in the constructor of the class:

        private final Object controlCars, controlMotorcycles; 
        public ParkingStats (ParkingCash cash) { 
          numberCars=0; 
          numberMotorcycles=0; 
          controlCars=new Object(); 
          controlMotorcycles=new Object(); 
          this.cash=cash; 
        }

  1. Finally, modify the methods that increment and decrement the number of cars and motorcycles, including the synchronized keyword. The numberCars attribute will be protected by the controlCars object, and the numberMotorcycles attribute will be protected by the controlMotorcycles object. You must also synchronize the getNumberCars() and getNumberMotorcycles() methods with the associated reference object:

public void carComeIn() { 
          synchronized (controlCars) { 
            numberCars++; 
          } 
        }
public void carGoOut() { 
synchronized (controlCars) { 
numberCars--; 
} 
cash.vehiclePay(); 
}

public void motoComeIn() { 
          synchronized (controlMotorcycles) { 
            numberMotorcycles++; 
          } 
        }
public void motoGoOut() { 
synchronized (controlMotorcycles) { 
numberMotorcycles--; 
} 
cash.vehiclePay(); 
}

  1. Execute the example now and see the difference when compared to the previous version.

How it works...


The following screenshot shows the output of the new version of the example. No matter how many times you execute it, you will always obtain the correct result:

java-multithreading-synchronize-threads-implement-critical-sections-img-1

Let's see the different uses of the synchronized keyword in the example:

  • First, we protected the vehiclePay() method. If two or more Sensor tasks call this method at the same time, only one will execute it and the rest will wait for their turn; therefore, the final amount will always be correct.
  • We used two different objects to control access to the car and motorcycle counters. This way, one Sensor task can modify the numberCars attribute and another Sensor task can modify the numberMotorcycles attribute at the same time; however, no two Sensor tasks will be able to modify the same attribute at the same time, so the final value of the counters will always be correct.


Finally, we also synchronized the getNumberCars() and getNumberMotorcycles() methods. Using the synchronized keyword, we can guarantee correct access to shared data in concurrent applications.

As mentioned at the introduction of this recipe, only one thread can access the methods of an object that uses the synchronized keyword in their declaration. If thread (A) is executing a synchronized method and thread (B) wants to execute another synchronized method of the same object, it will be blocked until thread (A) is finished. But if thread (B) has access to different objects of the same class, none of them will be blocked.

When you use the synchronized keyword to protect a block of code, you use an object as a parameter. JVM guarantees that only one thread can have access to all the blocks of code protected with this object (note that we always talk about objects, not classes).

We used the TimeUnit class as well. The TimeUnit class is an enumeration with the following constants: DAYSHOURSMICROSECONDSMILLISECONDSMINUTESNANOSECONDS, and SECONDS. These indicate the units of time we pass to the sleep method. In our case, we let the thread sleep for 50 milliseconds.

There's more...


The synchronized keyword penalizes the performance of the application, so you must only use it on methods that modify shared data in a concurrent environment. If you have multiple threads calling a synchronized method, only one will execute them at a time while the others will remain waiting.

If the operation doesn't use the synchronized keyword, all the threads can execute the operation at the same time, reducing the total execution time. If you know that a method will not be called by more than one thread, don't use the synchronized keyword. Anyway, if the class is designed for multithreading access, it should always be correct. You must promote correctness over performance. Also, you should include documentation in methods and classes in relation to their thread safety.

You can use recursive calls with synchronized methods. As the thread has access to the synchronized methods of an object, you can call other synchronized methods of that object, including the method that is being executed. It won't have to get access to the synchronized methods again.

We can use the synchronized keyword to protect access to a block of code instead of an entire method. We should use the synchronized keyword in this way to protect access to shared data, leaving the rest of the operations out of this block and obtaining better performance of the application. The objective is to have the critical section (the block of code that can be accessed only by one thread at a time) as short as possible.

Also, avoid calling blocking operations (for example, I/O operations) inside a critical section. We have used the synchronized keyword to protect access to the instruction that updates the number of persons in the building, leaving out the long operations of the block that don't use shared data. When you use the synchronized keyword in this way, you must pass an object reference as a parameter. Only one thread can access the synchronized code (blocks or methods) of this object. Normally, we will use the this keyword to reference the object that is executing the method:

  synchronized (this) { 
      // Java code 
    }


To summarize, we learnt to use the synchronized  keyword method for multithreading in Java to perform synchronization mechasim.

You read an excerpt from the book Java 9 Concurrency Cookbook - Second Edition. This book will help you master the art of fast, effective Java development with the power of concurrent and parallel programming.

Concurrency programming 101: Why do programmers hang by a thread?

How to create multithreaded applications in Qt

Getting Inside a C++ Multithreaded Application