Concurrency Control: Implementing Bakery Locks

Snippet of programming code in IDE
Published on

Concurrency Control: Implementing Bakery Locks in Java

Concurrency control is a critical aspect of multithreaded programming, especially in Java. In a multithreaded environment, multiple threads compete for shared resources, leading to potential race conditions and data inconsistency. To address this, synchronization mechanisms such as locks are employed to ensure mutual exclusion and maintain data integrity.

In this blog post, we'll delve into the implementation of Bakery Locks, a classic synchronization algorithm used to provide mutual exclusion in a multithreaded environment. We'll explore the concept behind Bakery Locks, and then proceed to implement a simple version of this algorithm in Java.

Understanding Bakery Locks

Bakery Locks, also known as Lamport's Bakery Algorithm, is a mutual exclusion algorithm designed to prevent simultaneous access to shared resources by multiple threads. The algorithm assigns each thread a unique ticket number based on their arrival order. Threads then enter a busy wait loop, waiting until it's their turn to access the critical section based on their ticket number.

The key idea behind Bakery Locks is to ensure fairness in granting access to the critical section by servicing threads in the order of their ticket numbers. This eliminates the possibility of starvation and ensures that all threads eventually get access to the critical section.

Implementing Bakery Locks in Java

Let's now proceed to implement a simplified version of the Bakery Locks algorithm in Java. We'll create a BakeryLock class with methods for acquiring and releasing the lock. We'll use the AtomicInteger class to keep track of ticket numbers for each thread and an array to indicate which threads are currently in the critical section.

import java.util.concurrent.atomic.AtomicInteger;

public class BakeryLock {
    private boolean[] choosing;
    private AtomicInteger[] ticket;

    public BakeryLock(int n) {
        choosing = new boolean[n];
        ticket = new AtomicInteger[n];

        for (int i = 0; i < n; i++) {
            ticket[i] = new AtomicInteger();
        }
    }

    public void lock(int threadId) {
        choosing[threadId] = true;
        ticket[threadId].set(getMaxTicket() + 1);
        choosing[threadId] = false;

        for (int i = 0; i < ticket.length; i++) {
            // Busy wait loop
            while ((i != threadId) && (choosing[i] || (ticket[i].get() != 0 && (ticket[threadId].get() > ticket[i].get())))) {
                // Do nothing
            }
        }
    }

    public void unlock(int threadId) {
        ticket[threadId].set(0);
    }

    private int getMaxTicket() {
        int max = Integer.MIN_VALUE;
        for (AtomicInteger t : ticket) {
            max = Math.max(max, t.get());
        }
        return max;
    }
}

In the above code, we create a BakeryLock class with methods for lock and unlock operations. We initialize the choosing array to track whether a thread is currently selecting a ticket, and the ticket array to hold ticket numbers for each thread.

When a thread calls the lock method, it sets choosing[threadId] to true, obtains the next ticket number, and then enters a busy wait loop until it is its turn to enter the critical section based on the ticket numbers of other threads.

The unlock method simply resets the ticket number of the thread to zero, indicating that it has exited the critical section.

Testing the Bakery Locks

To demonstrate the usage of the BakeryLock class, let's consider a simple multithreaded program that updates a shared counter within a critical section.

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class BakeryLockExample {
    private static int counter = 0;
    private static final int NUM_THREADS = 4;
    private static final int NUM_INCREMENTS = 100000;
    private static BakeryLock lock = new BakeryLock(NUM_THREADS);

    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(NUM_THREADS);

        for (int i = 0; i < NUM_THREADS; i++) {
            executor.execute(() -> {
                for (int j = 0; j < NUM_INCREMENTS; j++) {
                    lock.lock(i);
                    counter++;
                    lock.unlock(i);
                }
            });
        }

        executor.shutdown();

        while (!executor.isTerminated()) {
            // Wait for all threads to finish
        }

        System.out.println("Final Counter Value: " + counter);
    }
}

In this example, we create a BakeryLock instance with the specified number of threads. The main method creates a thread pool and executes tasks that increment the shared counter within the critical section. The lock and unlock methods are used to acquire and release the bakery lock, ensuring that only one thread accesses the critical section at a time.

In Conclusion, Here is What Matters

In this blog post, we've explored the concept of Bakery Locks and implemented a basic version of the algorithm in Java. Bakery Locks provide a fair and efficient mutual exclusion mechanism for multithreaded programs, ensuring that all threads have an equal opportunity to access critical sections.

Understanding concurrency control mechanisms like Bakery Locks is crucial for writing robust multithreaded applications in Java. By leveraging synchronization algorithms effectively, developers can prevent data races, ensure data integrity, and optimize resource utilization in concurrent environments.

By implementing and utilizing Bakery Locks, developers can enhance the performance and reliability of their Java applications in multithreaded scenarios, ultimately leading to a smoother and more efficient user experience.

In conclusion, mastering concurrency control mechanisms such as Bakery Locks empowers Java developers to build scalable, responsive, and thread-safe applications, laying the foundation for robust software systems in today's demanding computing landscape.

For more information on concurrency and multithreading in Java, refer to the official Java Concurrency documentation.

Feel free to experiment with Bakery Locks and explore more advanced synchronization mechanisms to further enhance your understanding of concurrency control in Java. Happy coding!