Marciano Dev

[Java] Mutex

1. Overview

In this tutorial, we’ll explore various methods to implement a mutex in Java, essential for managing shared resources in multithreaded environments.

2. Mutex

In the realm of multithreaded applications, the concurrent access of shared resources by two or more threads can lead to unpredictable outcomes, a phenomenon known as a race condition. These shared resources encompass vital elements such as data structures, input-output devices, files, and network connections.

A critical section of code refers to the segment of a program where shared resources are accessed. To prevent race conditions and maintain data integrity, synchronization of access to this critical section is paramount.

A mutex, or mutual exclusion, emerges as the fundamental synchronizer in this context. Acting as a lock, a mutex ensures that only one thread can execute the critical section at any given time. The process involves a thread acquiring the mutex before accessing the critical section and releasing it afterward. During this time, other threads attempting to enter the critical section are blocked, thus ensuring exclusive access and averting potential data corruption.

3. Synchronized Keyword

The synchronized keyword in Java is a built-in mechanism for synchronizing access to shared resources in multi threaded environments.

example 1

Explanation:

  1. Counter class: This class represents a simple counter with an integer value count.
  2. increment() method: This method increments the count variable in a synchronized manner. The synchronized keyword ensures that only one thread can execute this method at a time. When a thread enters this method, it locks the object (counter in this case), preventing other threads from accessing this method until the lock is released.
  3. SynchronizedExample class: This class contains the main() method where we create instances of the Counter class and multiple threads to access the counter concurrently.
  4. Thread objects: We create two Thread objects, thread1 and thread2, each with a lambda expression defining their behavior. Each thread calls the increment() method of the Counter object five times.
  5. Starting threads: We start both threads using the start() method.

When you run this program, you’ll see that the output demonstrates the synchronized behavior, ensuring that the count is incremented correctly without any race conditions. The synchronized keyword ensures mutual exclusion, preventing multiple threads from accessing the critical section simultaneously, thus maintaining data consistency.

4. ReentrantLock

ReentrantLock is a class that provides a more flexible alternative to intrinsic locks (synchronized blocks) in Java.

example 2

Explanation:

  1. Counter class: This class represents a simple counter with an integer value count and a ReentrantLock named lock.
  2. increment() method: This method increments the count variable using the ReentrantLock. We call lock.lock() to acquire the lock before accessing the critical section. We use a try-finally block to ensure that the lock is released even if an exception occurs during the execution of the critical section. After incrementing the count, we call lock.unlock() to release the lock.
  3. ReentrantLockExample class: This class contains the main() method where we create an instance of the Counter class and multiple threads to access the counter concurrently.
  4. Thread objects: We create two Thread objects, thread1 and thread2, each with a lambda expression defining their behavior. Each thread calls the increment() method of the Counter object five times.
  5. Starting threads: We start both threads using the start() method.

When you run this program, you’ll see that the output demonstrates the synchronized behavior achieved using ReentrantLock. The ReentrantLock provides more flexibility than synchronized keyword, such as the ability to try to acquire the lock without blocking, timed waiting, and more complex locking scenarios.

5. Semaphore

Semaphore is a synchronization primitive in Java that maintains a set of permits, which are used to control access to a shared resource.

Explanation:

  1. Printer class: This class represents a printer with a Semaphore named semaphore.
  2. Constructor: We initialize the semaphore with one permit, meaning only one thread can access the printer at a time.
  3. printDocument() method: This method simulates printing a document by acquiring a permit from the semaphore before printing each copy. The acquire() method blocks if no permit is available. After printing all copies, the permit is released using the release() method.
  4. SemaphoreExample class: This class contains the main() method where we create an instance of the Printer class and multiple threads to access the printer concurrently.
  5. Thread objects: We create two Thread objects, thread1 and thread2, each with a lambda expression defining their behavior. Each thread calls the printDocument() method of the Printer object to print documents concurrently.
  6. Starting threads: We start both threads using the start() method.

When you run this program, you’ll see that the output demonstrates how Semaphore ensures that only one thread can access the printer at a time, despite multiple threads attempting to print documents concurrently.

6. Conclusion

Each synchronization mechanism has its strengths and use cases. While synchronized keyword is suitable for simpler synchronization needs, ReentrantLock and Semaphore offer greater flexibility and control, making them preferable for more complex multi threaded applications.

By understanding the characteristics and capabilities of these synchronization mechanisms, Java developers can make informed decisions when designing and implementing multi threaded applications, ensuring thread safety and efficient resource management.

Leave a Reply

Your email address will not be published. Required fields are marked *