• Home
  • LLMs
  • Docker
  • Kubernetes
  • Java
  • All
  • About
Java | Locks and Synchronization
  1. Introduction
  2. Synchronizing a Method
  3. Synchronizing a Code Block
  4. Locking an Instance: Class or Object

  1. Introduction
    Locking is a mechanism that allows threads to have exclusive access to the synchronized methods of an instance. Synchronized methods of an instance locked by one thread are not accessible to other threads. Other threads must wait for the instance to be unlocked before they can attempt to acquire the lock on that instance and access its synchronized methods.

    Notes:
    • Each thread can hold one or more locks simultaneously.

    • An instance can be locked by only one thread at a time. This lock does not affect other instances, which can be locked by other threads concurrently.

    • Only the synchronized methods of an instance are affected by the lock acquired on that instance. Non-synchronized methods remain accessible to all threads at all times.

    • A thread that obtains a lock on an instance can access all the synchronized methods of that instance without additional locking overhead.

    • Synchronization applies only to methods or code blocks, not to individual variables or fields. However, synchronized methods can safely access and modify instance variables.

    • Java uses intrinsic locks (monitor locks) for synchronization. Every object has an associated monitor that can be locked by at most one thread at a time.
  2. Synchronizing a Method
    A method is synchronized when it is marked with the synchronized keyword.

    When a synchronized method is called, the current thread acquires a lock on the instance referred to by that call, provided that this instance is not already locked by another thread.

    If an instance is already locked by another thread, then no other thread can acquire a lock on this instance and, therefore, cannot execute any of its synchronized methods.

    Each thread that tries to acquire the lock on an already locked instance will be blocked (put in a waiting state) until the instance is unlocked.

    As long as the synchronized method is still executing, the thread holding the lock does not release it — even if it is no longer actively running (e.g., if it is sleeping or waiting for I/O).
    class MyClassA {
        private String value;
    
        public synchronized void foo() { // synchronized method: locks instance 'this'
            setValue(Thread.currentThread().getName() + " : value");
    
            try {
                Thread.sleep(1000); // force the current thread to sleep to give the other thread a chance to run
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt(); // restore interrupted status
                e.printStackTrace();
            }
    
            System.out.println(getValue());
        }
    
        private String getValue() {
            return value;
        }
    
        private void setValue(String value) {
            this.value = value;
        }
    }
    
    public class MainClass {
        public static void main(String[] args) {
            final MyClassA myClassA = new MyClassA();
    
            Runnable myRunnable1 = new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + " (start)");
    
                    myClassA.foo(); // the current thread will acquire a lock on the instance referred to by "myClassA"
    
                    System.out.println(Thread.currentThread().getName() + " (end)");
                }
            };
    
            Thread myThread1 = new Thread(myRunnable1, "myFirstThread");
            Thread myThread2 = new Thread(myRunnable1, "mySecondThread");
    
            myThread1.start();
    
            try {
                Thread.sleep(500); // force the "main" thread to sleep to ensure "myFirstThread" starts before "mySecondThread"
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                e.printStackTrace();
            }
    
            myThread2.start();
        }
    }
    Output:
    myFirstThread (start)
    mySecondThread (start)
    myFirstThread : value
    myFirstThread (end)
    mySecondThread : value
    mySecondThread (end)
    Note that "mySecondThread (start)" prints immediately, but "mySecondThread" waits for "myFirstThread" to complete the synchronized method before it can execute foo(). This demonstrates that the thread is blocked when trying to acquire the lock, not when starting.
  3. Synchronizing a Code Block
    The difference between synchronizing a method and synchronizing a code block is that the latter allows you to specify which object to use as the lock (monitor); whereas method synchronization always locks the instance whose method is being executed (this for instance methods).

    A synchronized method can always be rewritten as a synchronized code block. The previous example can be rewritten as follows:
    class MyClassA {
        private String value;
    
        public void foo() {
            synchronized (this) // synchronized code block: locks instance 'this'
            {
                setValue(Thread.currentThread().getName() + " : value");
    
                try {
                    Thread.sleep(1000); // force the current thread to sleep to give the other thread a chance to run
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt(); // restore interrupted status
                    e.printStackTrace();
                }
    
                System.out.println(getValue());
            }
        }
    
        private String getValue() {
            return value;
        }
    
        private void setValue(String value) {
            this.value = value;
        }
    }
  4. Locking an Instance: Class or Object
    When we refer to instances, we typically mean objects that are instances of user-defined classes. However, every Java class (source code) also has an associated instance of the Class class; this Class instance is automatically created by the JVM when the class is first loaded into memory.

    A Java class T can therefore have two types of instances in memory:
    • Class instance (static context): A single instance of the Class class representing the Java class T is automatically created by the JVM when the class T is first referenced.
      This instance can also be obtained explicitly using T.class or Class.forName("T").
      This instance serves as the monitor for static synchronized methods.
      When a static method is synchronized, the lock is applied to this Class instance.
      class T {
          static synchronized void foo() {
              // Equivalent to: synchronized(T.class) { ... }
          }
      
          void bar() {
              synchronized (T.class) {
                  // Explicitly locking on the Class instance
              }
          }
      }
    • Object instances (instance context): One or more instances representing objects created from the Java class T using the new operator (new T()).
      These instances are used to access non-static members of the class.
      When a non-static method is synchronized, the lock is applied to the specific instance on which the method is being executed (this).
      class R {
          synchronized void foo() {
              // Equivalent to: synchronized(this) { ... }
          }
      
          void bar() {
              synchronized (this) {
                  // Explicitly locking on this instance
              }
          }
      }
    Note: The lock obtained when calling a synchronized static method of a class is completely independent from the lock obtained when calling a synchronized non-static method of the same class. Both locks can be held by different threads simultaneously without any interference, as they are locking different objects (Class instance vs. object instance).
    class P {
        static synchronized void staticMethod() {
            // Locks P.class
        }
    
        synchronized void instanceMethod() {
            // Locks 'this' instance
        }
    }
    Two threads can execute staticMethod() and instanceMethod() concurrently because they use different monitors.
© 2025  mtitek