• 12 hours
  • Hard

Free online content available in this course.

course.header.alt.is_video

course.header.alt.is_certifying

Got it!

Last updated on 11/8/22

Avoid Thread Interference via Synchronization and Guarded Blocks

In this chapter, you'll learn about popular methodologies for how to keep threads from interfering with each other's work, namely synchronized scopes, synchronized variables, and guarded blocks. While not as modern as the methodology you'll see in the next chapter, they're still important to recognize and understand, as you'll encounter them in the workplace.

Working on One Thread at a Time With Synchronized Scopes

As you may recall, atomic types provide methods that guarantee that the precise operations they carry out are thread-safe. These methods are typically used to inspect a value and replace it. However, they do not allow you to create and define your own atomic sequence of operations.

Java also has another mechanism that you can use to guarantee that a specific set of statements in a particular instance of a class can only ever get executed by one thread at a time. To achieve this, you can use the synchronized keyword as a gate, ensuring that statements that can not be safely executed concurrently are always executed in serial.

There are two ways you can use this to guard a block of code:

Use of Synchronized

Description

Example

Method modifier

Placing synchronized in front of your method declaration to only allow one execution per instance of that class.

 

 public sychronized void myMethod() { // only one thread can execute this }

As a function with an argument

Placing synchronized in front of a block of code to allow one execution across all threads using the same argument reference.

synchronized(this) {
    // critical section to be run by one thread.
}

Let's check out how each of these options work.

Option 1: Synchronized as a Method Modifier

In the first case, using synchronized it as a modifier on a method is a little like having a meeting where only one person may talk at a time. I used to do this in meetings where we had a habit of talking over one another. We used a popular solution, passing a shell around. Only the person holding the shell can speak. 🐚

Adding synchronized to your method is the same. It results in only one thread at a time being able to execute that method on a specific instance of its class. You call the instance a monitor for your lock. All code entering an area synchronized on the same monitor suspends. For instance, any other threads using the same instance of a class containing your synchronized method. In other words, any methods marked synchronized also wait on each other, so that only one can be executed at any point in time.

Having many synchronized methods is like a team with many meetings and only one shell. Regardless of the number of meetings in progress, only one person may speak at a time.

Option 2: Synchronized as a function

In the second case, you can decide to use something other than a talking shell. When you add synchronized on a method, the critical section is restricted to all threads executing that method. If instead, you use synchronized(someObject) as a function, you can get it to restrict access to a block of code following this declaration. Using synchronized as a function can look like the following code snippet:

public void myUnsynchronizedMethod {
    // some unsychronized code
    callToCode();
    synchronized(this) {
        // critical section
    }
    // some more unsynchronized code
    callToCode();
}
  • Lines 1 to 3: Method declaration and unsynchronized code.

  • Lines 4 to 6: This block of code is synchronized across all threads using the same value of this. That is, they are calling the method  myUnsychronizedMethod  against the same instance of this class.

  • Line 8: You execute some more unsynchronized code.

The value you pass the function is the equivalent of the talking shell;  it's the monitor used for synchronization. Unlike method  synchronization, where the monitor is implicitly the current instance of a class, you can define any instance here. Use this monitor to restrict access to your block of code.

In most concurrent code, you often pass in a monitor of this, which is the reference to the current object instance. Doing this allows you the same level of control as using  synchronized  as a method modifier but across a very specific set of statements.

For instance, check out the following execution:

Two threads execute the same code. The first to execute the synchronized block, can have exclusive access to that block of code.
Code in synchronized blocks will block other threads.

In this method, you can see that two threads execute to a point where you see the synchronized keyword.

But can't I just refactor my code and use synchronized  with smaller methods?

Please do! However, this might prevent you from being able to improve the concurrency of your code.  Synchronized  as a method modifier stops all threads using any other synchronized method on the same instance.

You might decide to change your implicit lock from this to a few different objects that you can use as a talking shell. Using the  synchronized()  function with a specific object makes it easier to effect such a change later. You might want to do this if your performance requirements direct you to optimize your code to avoid blocking every synchronization on that instance of a particular class. 

Using Synchronized Variables

As you saw with atomic variables, you often want to protect a shared mutable variable from being corrupted by multiple threads reading and updating inconsistently. You have two tools at your hands to aid you in doing this:

  • The volatile  keyword which ensures that any read of a variable will always reflect the latest update by any writing thread.

  • The use of synchronized blocks to ensure that critical sections relating to the reading and writing a variable are thread-safe.

As you've already seen, you can change the implicit lock used for synchronization by calling the function, synchronized(monitorObject). This change ensures that multiple threads synchronizing on the same value argument have to take turns at executing the following code.

In this case, select some instantiated object as your monitor or talking shell, making sure that everything is sharing the same blocks while it is synchronized.

Can I use this to synchronize any variable?

Java does not allow you to synchronize changes to a variable; however, you can synchronize changes using a specific object reference or class instance. That object or reference is used for coordination.

What this means is that if you want to synchronize on a particular field of a class, you can encapsulate it in a class. Let's do this for our subtotal example!

Try It Out for Yourself!

Fire up JShell and let's implement a thread-safe subtotal. Paste each chunk into the interpreter.

Step 1: First, create a class to store your subtotal. Wrap the subtotal in a new class so that you can synchronize on the same Java instance without reassigning it.

public class SubTotal {
    public volatile Integer value = 0;
}

 This is a plain old Java class (POJO) with one exception. The value field is marked with the volatile field modifier. This causes Java to ensure that if two threads attempt to modify and read from it at the same time, they reflect the new modified value. This reduces the risk of reading stale data.

 Step 2: Next, create a class that creates an instance of SubTotal on which you can synchronize.

public class Adder extends Thread {
    static volatile SubTotal subtotal = new SubTotal();
}

This creates a class with a static SubTotal instance that you also make sure is consistent, using volatile.

Step 3: Create a run method which adds a value of 20 for each running thread.

public class Adder extends Thread {
    public static volatile SubTotal subtotal = new SubTotal();
    public void run() {
        synchronized(subtotal) {
            // Don't do this in production code
            // This should be encapsulated.
            subtotal.value = subtotal.value+20;
        }
    }
}

JShell will inform you that you have "replaced class Adder." Now, let's break down this code:

  • Line 1 to 2: As before, you have a local instance of  SubTotal().

  • Line 3: Override the run method.

  • Line 4: Synchronize on the static instance of SubTotal defined at Line 2.

  • Line 7: Increment your subtotal by 20. We're updating a public variable directly, for the sake of illustration.

Step 4: Let's create some threads now!

jshell> Adder thread1 = new Adder();
thread1 ==> Thread[Thread-0,5,main]

jshell> Adder thread2 = new Adder();
thread2 ==> Thread[Thread-1,5,main]

jshell> Adder thread3 = new Adder();
thread3 ==> Thread[Thread-2,5,main]

As you can see there are three instances of Adder from calling its constructor.

Step 5: Let's run those threads!

jshell> thread1.start(); thread2.start(); thread3.start();

jshell> Adder.subtotal.value
$9 ==> 60

OK, let's break this down:

  • Line 1: Run all three threads by calling their start() methods.

  • Line 3: Inspect the updated value of the subtotal. As you can see, this is set to 60, representing the three additions of 20.

How do I know that this is safer?

Since we're avoiding hard-to-reproduce issues, you'll have to trust the specification of Java to an extent. I would recommend that you try and recreate the issue by writing the same code without synchronization. Since hard-to-predict ordering issues cause the issues of concurrency in the JVM, you can try using more threads and running the code multiple times. Time-box yourself to about an hour, though - that should give you enough information! 😉

Avoiding Jamming Your Program With Deadlocks!

When synchronizing your code, be careful that threads do not become stuck waiting on one another. Consider this class:

public class Deadlocker {
	enum Reachability {
		REACHED,
		UNREACHED
	}

	private static Reachability unReachablePoint = Reachability.UNREACHED;

	public void runOnThread1() {
	    // Blocks Line 23
		synchronized(Boolean.class) {
		    // Blocked by Line 21
			synchronized(Deadlocker.class) {
				unReachablePoint = Reachability.REACHED;
			}
		}
	}

	public void runOnThread2() {
	    // Blocks Line 13
		synchronized(Deadlocker.class) {
		    // Blocked by Line 11
			synchronized(Boolean.class) {
				unReachablePoint = Reachability.REACHED;
			}
		}
	}
}

The static variable  unReachablePoint  is set to  UNREACHED, and there are two methods that could potentially set it to  REACHED. It's possible that neither will modify it if both methods runOnThread1() and runOnThread2() are executed at the same time.

Look through the code above again and see if you can spot why. I'll wait. 😇

Did you have a look? Let's consider this together and understand the risk:

  • Line 11 Thread 1- Imagine  runOnThread1()  is called by Thread 1 and this statement is executed first. Since no other thread has synchronized on  Boolean.class, the thread gets past this point.

  • Line 21 Thread 2 - Imagine that  runOnThread2() is executed next by Thread 2 and reaches Line 21. Since no threads have synchronized on  Deadlocker.class, Thread 2 now has exclusive access to this lock.

  • Line 13 Thread 1 - When Thread 1 reaches this line, it needs to wait for  Deadlocker.class  to be released by Thread 2. This only happens if Thread 2 finishes running Line 24. At this point, Thread 1 also releases its lock on  Boolean.class.

  • Line 23 Thread 2 - When Thread 2 reaches this statement, it won't be able to continue to Line 24 as it needs to wait for Thread 1 to release its lock on Boolean.class. As you've seen, this won't happen until Thread 2 releases its lock on  Deadlocker.class.

As you can tell, both threads wait on one another indefinitely and jam the program. We'll never continue to the critical sections of the code. Since both threads are waiting on one another, this is called deadlock. They are dead waiting on one another's locks, as is our program.

How do I avoid deadlock?

To avoid deadlock, you should always make sure that multiple threads acquire the same synchronization locks in the same order. If both threads synchronized on Boolean.class and then Deadlocker.class (or the other way around), we would have avoided the risk of deadlock.

Will this code always deadlock?

The bad thing about deadlock is that it might creep up on you out of the blue if you've not designed to defend against it. If either Thread 1 or Thread 2 were able to acquire both their synchronization before the other thread, we'd avoid deadlock. As usual, it's down to the scheduler and timing. 

As you'll see, there are many other ways to coordinate execution between your threads. In all cases, watch out for deadlocks.

Guarding Access to the Point of no Return With Guarded Blocks

One of the most primitive forms of synchronizing between threads is known as the guarded block. This form is a pattern or common style of implementation used to coordinate between multiple threads with synchronized methods. It helps the threads coordinate when two methods must take turns running. For instance, one might be waiting for a resource output by the other, such as values needing to be summed before averaging them.

The pattern depends on two types of methods: wait and notify, which all Java classes inherit from the base object class. Their basic behavior is summarized as:

Method

Behavior

Example

Object.wait()

Block the current thread in a WAIT state until Object.notify() is called on the same instance.

 

Object lock = new Object();

lock.wait();

Object.notify()

Unblock at most one thread which is in a WAIT state resulting from calling Object.wait().

lock.notify();

Object.notifyAll()

Unblock all the threads which are in a WAIT state, resulting from calling Object.wait().

lock.notifyAll();

Here's an example program that prints a word when one is available. If it prints before the word is set, you'll see a NullPointerException. The pattern for implementing a guarded block is generally structured as follows:

public class WordPrinter {

    // Create monitor
    volatile Object lock = new Object();
    // Create signal for resource sharing
    volatile Boolean readyToPrint = false;

    // Resources used for computation
    String wordToPrint;

    // Waits for a wordToPrint to be set
    public void printWord() throw InterruptedException {
        synchronized(lock) {
            while(!readyToPrint) {
                lock.wait();
            }
            System.out.println("The word is " + wordToPrint);
            readyToPrint=false;
        }
    }

    // Sets a wordToPrint
    public void setWordToPrint(String wordToPrint) {
        synchronized(lock) {
            this.wordToPrint = wordToPrint;
            // we are ready
            readyToPrint=true;
            lock.notify();
        }
    }
}

Let's break this down!

  • Line 4: Create a volatile object instance used as a monitor for synchronization between multiple threads. Eg.,  Object lock.

  • Line 6: volatile variable used for communication is shared between multiple threads. Eg., Boolean readyToPrint=false;

  • Line 13: In the printWord method, synchronize on the lock and allow a single thread through.

  • Threads synchronize on the lock object, using it as a monitor. Eg.,  synchronize(lock).

  • Line 14: Go into a while loop waiting for readyToPrint  to be true. This is a check to ensure that you're ready to print a result. 

  • Line 15: If you are not ready to print, wait for this condition to be true by calling  Object.wait(). If we had not called this method, we'd still wait for the condition, but burn CPU cycles by keeping the thread active and continuously checking the condition. Instead, the thread goes into a WAIT state until the condition can be satisfied.

  •  Line 17: You can print out the result when this line is reached. You only reach this line when  Object.wait()  has received a notification.

  • Line 23-29: The  setWordToPrint  method receives a word. It synchronizes on the same object before setting the word, signaling that readyToPrint=true and unsuspending another thread by calling  Object.notify().

Try It Out for Yourself!

Paste the above code into JShell so you can try it out. Then do the following to use it concurrently:

Step 1: Create an instance of WordPrinter which you'll share:

jshell> WordPrinter wordPrinter = new WordPrinter();
wordPrinter ==> WordPrinter@63c12fb0

Step 2: Create a thread which calls a lambda  ()->wordPrinter.printWord()  and start it.

jshell> Thread printerThread = new Thread(()->printer.printWord())
printerThread ==> Thread[Thread-7,5,main]

jshell> printerThread.start()

Notice how it's not doing anything. That's because it's waiting!

Step 3: Create a thread which calls a lambda ()->wordPrinter.setWordToPrint("Pluto"). 

jshell> Thread wordSetterThread = new Thread(()->printer.setWordToPrint("Pluto!"))
wordSetterThread ==> Thread[Thread-8,5,main]

jshell> wordSetterThread.start()
The word is Pluto!

Can you see how starting the thread at line which called  setWordToPrint("Pluto!")  kicked off the  notify()  required to print the word from the first thread?

Let's Recap!

  • A critical section consists of a set of Java statements that much be executed by only one thread at a time. This is usually a set of sequential statements that risks corrupting your data or program logic if run concurrently.

  • To mark a whole method as a critical section, we place the keyword synchronized  before the method.

  • Synchronizing a method results in only one thread able to run that method at a time, per instance of its class

  • Calling  synchronized(<Some Reference>;)  as a function with an argument before a block of code containing your critical section allows you to use any object reference as a monitor for synchronizing threads. Of all threads sharing that object, only one executes any block of code synchronized with the same object.

  • Java's object class provides a  wait()  and  notify()  methods. Threads can use these on any object to, respectively, wait for a signal, and also notify other threads that they may proceed. These are low-level tools that give you a lot of control and increase your risk of getting things wrong. Avoid them if you can. 

In the next chapter, you'll learn about more ReentrantLocks and the Lock API, which are a more modern style of keeping threads from interfering with each other. 

Example of certificate of achievement
Example of certificate of achievement