Logo

dev-resources.site

for different kinds of informations.

Synchronizing Threads with Semaphores: Practicing Concurrency in Java - LeetCode Problem 1115, "Print FooBar Alternately"

Published at
12/15/2024
Categories
webdev
java
multithreading
leetcode
Author
budiwidhiyanto
Author
14 person written this
budiwidhiyanto
open
Synchronizing Threads with Semaphores: Practicing Concurrency in Java - LeetCode Problem 1115, "Print FooBar Alternately"

Introduction to Concurrency
In software development, concurrency allows multiple processes or threads to execute simultaneously, often leading to faster execution times and more efficient use of resources. However, effective concurrency management is crucial to avoid issues like race conditions, deadlocks, and inconsistent states. This is where synchronization mechanisms such as semaphores are essential.

Understanding Semaphores
A semaphore is a synchronization tool that controls access to a common resource by multiple threads in a concurrent system. It acts like a counter that regulates how many threads can access a specific part of the code at the same time. The semaphore operations are:

  • Acquire: Decreases the semaphore's counter if it's above zero, allowing the thread to proceed. If it's zero, the thread is blocked until another thread releases the semaphore.
  • Release: Increments the semaphore's counter, signaling that the thread has finished using the resource, allowing other waiting threads to proceed.

Semaphores are particularly useful for managing dependencies between tasks or ensuring that certain parts of the program run in a specific order.

Problem Description

class FooBar {
  public void foo() {
    for (int i = 0; i < n; i++) {
      print("foo");
    }
  }

  public void bar() {
    for (int i = 0; i < n; i++) {
      print("bar");
    }
  }
}

Enter fullscreen mode Exit fullscreen mode

We are given a class FooBar with two methods, foo() and bar(). These methods are designed to print "foo" and "bar," respectively. The challenge is to modify the class so that when these methods are executed by two different threads, the output consistently reads "foobar" repeated n times. This must be achieved regardless of the threads' execution order by the scheduler.

This problem is sourced from LeetCode, specifically from Problem 1115, "Print FooBar Alternately".

Solution Approach
To ensure the correct order of execution ("foo" followed by "bar"), we can use semaphores to coordinate the threads. Here’s how we can modify the existing FooBar class using semaphores to achieve the desired behavior:

  1. Introduce Two Semaphores: One semaphore (semFoo) will control the execution of foo(), and the other (semBar) will control bar().
  2. Initialize Semaphores: semFoo is initialized with a permit (count = 1), allowing foo() to execute first. semBar is initialized with no permits (count = 0), preventing bar() from executing until foo() is done.
  3. Modify the Methods: Each method must acquire its semaphore before proceeding and release the other semaphore after it has performed its task.

Here is the modified version of the FooBar class:

class FooBar {
    private int n;
    private Semaphore semFoo = new Semaphore(1); // Initially, allow foo() to proceed
    private Semaphore semBar = new Semaphore(0); // Initially, prevent bar() from proceeding

    public FooBar(int n) {
        this.n = n;
    }

    public void foo(Runnable printFoo) throws InterruptedException {
        for (int i = 0; i < n; i++) {
            semFoo.acquire(); // Wait until it's allowed to proceed
            printFoo.run();   // This will print "foo"
            semBar.release(); // Allow bar() to proceed
        }
    }

    public void bar(Runnable printBar) throws InterruptedException {
        for (int i = 0; i < n; i++) {
            semBar.acquire(); // Wait until it's allowed to proceed
            printBar.run();   // This will print "bar"
            semFoo.release(); // Allow foo() to proceed again
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Running the Code
To demonstrate this solution, we can create an instance of FooBar and start two threads, each responsible for one of the methods:

public class Main {
    public static void main(String[] args) {
        FooBar fooBar = new FooBar(2); // Example for n = 2

        Thread threadA = new Thread(() -> {
            try {
                fooBar.foo(() -> System.out.print("foo"));
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });

        Thread threadB = new Thread(() -> {
            try {
                fooBar.bar(() -> System.out.print("bar"));
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });

        threadA.start();
        threadB.start();
    }
}
Enter fullscreen mode Exit fullscreen mode

This setup ensures that the output on the console will be "foobarfoobar" for n = 2, demonstrating the power of semaphores in controlling the execution order of threads in Java applications.

Image description

multithreading Article's
30 articles in total
Favicon
Python 3.13: The Gateway to High-Performance Multithreading Without GIL
Favicon
# Boost Your Python Tasks with `ThreadPoolExecutor`
Favicon
ReentrantReadWriteLock
Favicon
ReentrantLock in Java
Favicon
Synchronizing Threads with Semaphores: Practicing Concurrency in Java - LeetCode Problem 1115, "Print FooBar Alternately"
Favicon
Effective Ways to Use Locks in Kotlin
Favicon
Python Multithreading and Multiprocessing
Favicon
Introducing Robogator for PS and C#
Favicon
Multithreading Concepts Part 3 : Deadlock
Favicon
Multithreading Concepts Part 2 : Starvation
Favicon
Parallelism, Asynchronization, Multi-threading, & Multi-processing
Favicon
Using WebSocket with Python
Favicon
Power of Java Virtual Threads: A Deep Dive into Scalable Concurrency
Favicon
GIL "removal" for Python true multi threading
Favicon
Optimizing Your Development Machine: How Many Cores and Threads Do You Need for Programming?
Favicon
Multithreading Concepts Part 1: Atomicity and Immutability
Favicon
The Benefits of Having More Threads than Cores: Unlocking the Power of Multi-threading in Modern Computing
Favicon
Mastering Java Collections with Multithreading: Best Practices and Practical Examples
Favicon
Understanding Multithreading: Inner Workings and Key Concepts
Favicon
Handling Concurrency in C#: A Guide to Multithreading and Parallelism
Favicon
MultiThreading vs MultiProcessing
Favicon
Achieving multi-threading by creating threads manually in Swift
Favicon
Multithreading in Java : A Deep Dive
Favicon
Understanding std::unique_lock and std::shared_lock in C++
Favicon
Swift Concurrency
Favicon
Mastering Multithreading in C Programming: A Deep Dive with In-Depth Explanations and Advanced Concepts
Favicon
Understanding Multithreading in Python
Favicon
Introduction to Monitor Class in C#
Favicon
Deep Dive Into Race Condition Problem inΒ .NET
Favicon
Goroutines: Solving the problem of efficient multithreading

Featured ones: