Neko

Post

Mastering Synchronization. Best Practices and Patterns in Java

image

If you are interested in multithreading and concurrency in Java, you may be interested in these articles:

  1. Mastering Synchronization: Best Practices and Patterns in Java
  2. Navigating Java’s Multithreading Terrain Pt. 1. Dining Philosophers
  3. Navigating Java’s Multithreading Terrain Pt. 2
  4. Unlocking Concurrent Power: A Guide to java.util.concurrent Pt. 1
  5. Unlocking Concurrent Power: A Guide to java.util.concurrent Pt. 2.
  6. Concurrency in Java: Best Practices and Performance Optimization

Synchronizing a Method

1
2
3
public synchronized void synchronizedMethod() {
    // Synchronized method body
}

The synchronizedMethod is declared with the synchronized keyword, which means that only one thread can execute this method at a time. This synchronization is on the object level, so if you have multiple instances of the class, each instance will have its own lock.

Synchronizing a Code Block

1
2
3
4
5
6
7
8
9
public void someMethod() {
    // Non-synchronized code
    
    synchronized (this) {
        // Synchronized code block
    }
    
    // Non-synchronized code
}

In the someMethod example, the synchronized block is synchronized on the current instance (this). You can also synchronize on other objects, but it’s a common practice to use this for instance-level synchronization.

When a thread enters a synchronized method or block, it acquires the intrinsic lock (also known as monitor lock) associated with the object on which the method or block is synchronized. Other threads attempting to enter the synchronized method or block must wait until the lock is released by the thread currently executing it.

Synchronization is important for ensuring thread safety in multi-threaded applications where multiple threads access shared resources concurrently. However, excessive synchronization can lead to performance overhead and potential deadlocks, so it’s essential to use synchronization judiciously and only where necessary.

java.util.concurrent.locks

In Java, you can also use explicit locks provided by the java.util.concurrent.locks package to achieve synchronization. This gives you more flexibility and control compared to intrinsic locks used with the synchronized keyword. Here’s an example of how to use explicit locks:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class SynchronizedExample {
    private final Lock lock = new ReentrantLock();

    public void synchronizedMethod() {
        lock.lock(); // Acquire the lock
        try {
            // Synchronized method body
        } finally {
            lock.unlock(); // Release the lock in a finally block
        }
    }

    public void someMethod() {
        // Non-synchronized code

        lock.lock(); // Acquire the lock
        try {
            // Synchronized code block
        } finally {
            lock.unlock(); // Release the lock in a finally block
        }

        // Non-synchronized code
    }
}
  • We create an instance of ReentrantLock, which implements the Lock interface. This lock provides more control over synchronization than intrinsic locks.
  • In the synchronizedMethod, we acquire the lock using the lock() method before executing the synchronized method body. We release the lock using the unlock() method in a finally block to ensure that the lock is always released, even if an exception occurs.
  • In the someMethod, we acquire the lock before entering the synchronized code block and release it afterward in the same manner.

Explicit locks offer advantages such as the ability to specify timeouts, condition variables, and more fine-grained control over locking compared to intrinsic locks. However, they also require more careful management, as failing to release the lock properly can lead to deadlocks. Therefore, it’s essential to always release the lock in a finally block to ensure proper synchronization.

Both intrinsic locks (synchronized blocks/methods) and explicit locks (using java.util.concurrent.locks.Lock) have their advantages and are suitable for different scenarios. Here’s a comparison to help you decide which is better for your situation:

Intrinsic locks (synchronized blocks/methods):

  • Simplicity: Using synchronized blocks/methods is simpler and requires less boilerplate code compared to explicit locks.
  • Ease of use: Synchronized blocks/methods are easier to use and understand, making them a good choice for simpler synchronization needs.
  • Implicit release: Intrinsic locks are automatically released when the synchronized block/method exits, reducing the risk of forgetting to release the lock. Explicit locks (java.util.concurrent.locks.Lock):
  • Flexibility: Explicit locks offer more flexibility and control over synchronization, allowing you to specify timeouts, condition variables, and try-locking mechanisms.
  • Fine-grained control: With explicit locks, you have finer-grained control over locking, enabling you to implement more complex synchronization patterns.
  • Performance: In some cases, explicit locks may offer better performance compared to intrinsic locks, especially when dealing with highly contended locks or when using advanced features like try-locking.

In general, if you have simple synchronization needs and want a straightforward solution, intrinsic locks (synchronized blocks/methods) are a good choice. However, if you need more control over synchronization or require advanced features like timeouts and condition variables, explicit locks (java.util.concurrent.locks.Lock) may be more suitable.

Ultimately, the choice between intrinsic locks and explicit locks depends on your specific requirements, preferences, and the complexity of your synchronization needs.

You can find some examples at Github.

This post is licensed under CC BY 4.0 by the author.