Skip to main content

Synchronized Keyword in Java

In Java threading support, threads mostly communicate with each other via shared objects or shared member variables within the same object. Three type of complications can arise from this when multiple threads are allowed to access the same piece of memory.

  1. Thread Interference. Different threads access (read and write) the same data. This can lead to race conditions. The program behavior (what gets stored in the shared memory) depends on the order in which threads get access. This can cause non-deterministic behavior.
  2. Memory Consistency Errors. If multiple threads are updating the same variable, they can see a stale (inconsistent) value of a variable.
  3. Thread Contention. If locks are not used correctly, threads can get in each other's way, and slow down or sometimes even have to be killed by Java.

Let's look at the first two problems. If two threads access the same variable, it is possible for them to get in each other's way. That is because Java might switch execution from one thread to another even midway through a simple, seemingly atomic instruction. For example, two threads incrementing the same variable could simply 'loose' one of the two increments.

The solution is to make sure that a section of code that should be atomic is accessed by one thread at a time. Restricting access to an object or a variable - akin to locking the variable so that only one thread can access at a time - is widely used, e.g., in databases.

Locking variables correctly can eliminate the first two problems - Thread Interference and Memory Consistency Errors, but it slows down performance, and can lead to the third problem - Thread Contention issues, namely, Starvation, Livelock, Deadlock, to name a few. This essentially means that a particular thread can no longer make progress.

The Synchronized Keyword

Every object in Java has a lock associated with it. This lock is called the intrinsic lock or monitor. It helps in restricting access to the object. This lock is usually always open, i.e., any number of threads can access the object simultaneously. It is, however, possible to specify that a thread can only execute a section of code once it has acquired the lock on some object. If some other thread currently holds that lock, the current thread must wait its turn.

This ability to give the lock to only one thread is achieved using the "synchronized" keyword. The synchronized keyword is used to activate the intrinsic lock on any object and this lock can be used to restrict access to a section of the code.

Let's see how we may create a counter that is thread-safe. 
public class SynchronizedCounter {
    private int c = 0;
    
    public synchronized void increment() {
        c++;
    }

    public synchronized void decrement() {
        c--;
    }

    public synchronized int value() {
        return c;
    }
}
Any method in Java can be marked as synchronized. Doing so means that only one thread can be executing this member function on this object at a given point in time. This means
  • The "only-one-thread-at-a-time" restriction applies to the same method of the same object
  • If the method does something to a static class variable, errors can still result
Marking a method as synchronized is a shortcut to marking the entire body of the method as synchronized on 'this', i.e., the object in question. We can synchronize only a particular block of code using synchronized keyword.
public void addName(String name) {
    synchronized(this) {
        lastName = name;
        nameCount++;
    }
    nameList.add(name);
}

A thread never gets blocked on itself. This means that one synchronized method of an object can always call another synchronized method of the same object without blocking.

In general, any object can be used as a lock using the synchronized statement.

Thread Contention

Synchronization and locks are powerful and they can be misused. Incorrecly used, they can exacerbate thread contention. Some of the thread contention issues manifest themselves into -
  • Deadlock. Two threads, each is blocked on a lock held by the other. For example, if there are two threads - T1 and T2 and two locks - L1 and L2. If T1 holds L1 and T2 holds L2 and T1 needs L2 and/or T2 needs L1 to proceed, they are in a deadlock condition, i.e., they are stalled completely.
  • Livelock. Two threads keep blocking on locks held by the each other repeatedly. 
  • Starvation. Some threads keep acquiring locks greedily, and cause other threads to be unable to get anything done.

Comments

Popular posts from this blog

Jenkins CI Pipeline for Security Scans

This document describes the set of Jenkins CI Pipeline steps currently in use. Context Diagram Jenkins Workflow OCCNE CI Job Gradle Build  - using gradle, the available source is scanned using OWASP dependency checker, then a number of docker containers are created. Static Scan  - using the McAfee malware scanner container (created in the  Gradle Build  step), all created docker containers are scanned for malware. Verify Build  - Each docker container is loaded and the self test method is executed. Deploy  - An  OCCNE Deployment  job is created and invoked.  ( OCCNE CI  Jobs may run in parallel -  OCCNE Deploy  Jobs are serialized.) OCCNE Deploy Job Prepare Deploy  - Wipe out any old cluster artifacts - get ready for a fresh deploy for (container in  OS_Install, DB_Install,. K8s_install, Cfg_Install ) do: Deploy_{{container}}  - runs the named docker container Test_Deploy_{{container}}  - verifies that the d...

GoF patterns

Why learn GoF Design Patterns? Design patterns help you find out patterns in your code. It helps to visualize your code at a higher level and decompose it into logical units.   What are Design Patterns? Design patterns are canonical solutions to recurring problems. They are different from a library that is called from your code. Neither are they framework which is a complicated collection of libraries. Frameworks typically calls your code. The 24 design patterns covered in GoF book can be divided into three categories. Creational Patterns . These patterns seek to answer - "How should objects be created?".  Examples are - Factory, Abstract Factory, Singleton, Builder, Prototype, Dependency Injection. They usually seek to decouple the construction of an object from its use. There are advantages to doing this. Hide implementation of an object, only reveal its interface.  Defer instantiation until run-time.  Allow creation of finite number of instances.  Have f...

Git Workflow For Multiple Repositories

Introduction Imagine a situation where you have to work off multiple repositories hosted on multiple hosts. Let us say there are two hosts - ALM and Cloudlab. These repositories on these hosts are managed by two separate teams - ALM and Policy respectively. By setting up a Git workflow across Cloudlab and ALM instances, we can create a development environment for Policy developers working on ATS. This allows them to make changes to the  step files  and approve them within Policy team. Both teams - Policy and ALM, can work independently.  All development and code review is done in the Cloudlab instance. Once changes pass the team's quality assurance review, they are deployed to ALM instance as desired.  This article describes the method for Setting up a Git workflow among multiple repositories across Cloudlab and ALM How to keep those repositories in sync How to use another repo within a given repo Developer Workflow The overall process is as follows: Create a lo...