# Concurrency

#### [1114. Print in Order](https://leetcode.com/problems/print-in-order/)

{% tabs %}
{% tab title="Synchronized" %}

```java
class Foo {
    private int counter = 1;
    public Foo() {
    }

    public void first(Runnable printFirst) throws InterruptedException {
        synchronized (this) {
            while (counter != 1) {
                this.wait();
            }
            printFirst.run();
            counter = 2;
            this.notifyAll();
        }
        // printFirst.run() outputs "first". Do not change or remove this line.
    }

    public void second(Runnable printSecond) throws InterruptedException {
        synchronized (this) {
            while (counter != 2) {
                this.wait();
            }
            printSecond.run();
            counter = 3;
            this.notifyAll();
        }
        // printSecond.run() outputs "second". Do not change or remove this line.
        // printSecond.run();
    }

    public void third(Runnable printThird) throws InterruptedException {
        synchronized (this) {
            while (counter != 3) {
                this.wait();
            }
            printThird.run();
            counter = 1;
            this.notifyAll();
        }
    }
}
```

{% endtab %}

{% tab title="ReentrantLock" %}

```java
class Foo {
    private final ReentrantLock lock = new ReentrantLock();
    private final Condition condition = lock.newCondition();
    private volatile int count = 1;
    public Foo() {
        
    }

    public void first(Runnable printFirst) throws InterruptedException {
        lock.lock();
        try {
            while (count != 1) {
                condition.await();
            }
            // printFirst.run() outputs "first". Do not change or remove this line.
            printFirst.run();
            count = 2;
            condition.signalAll();
        } finally {
            lock.unlock();
        }
    }

    public void second(Runnable printSecond) throws InterruptedException {
        lock.lock();
        try {
            while (count != 2) {
                condition.await();
            }
            printSecond.run();
            count = 3;
            condition.signalAll();
        } finally {
            lock.unlock();
        }
        // printSecond.run() outputs "second". Do not change or remove this line.
        
    }

    public void third(Runnable printThird) throws InterruptedException {
        lock.lock();
        try {
            while (count != 3) {
                condition.await();
            }
            printThird.run();
            count = 1;
            condition.signalAll();
        } finally {
            lock.unlock();
        }
        
        // // printThird.run() outputs "third". Do not change or remove this line.
        // printThird.run();
    }
}
```

{% endtab %}

{% tab title="Semaphore" %}

```java
class Foo {
    private Semaphore firstSem = new Semaphore(1);
    private Semaphore secondSem = new Semaphore(0);
    private Semaphore thirdSem = new Semaphore(0);
    public Foo() {
    }

    public void first(Runnable printFirst) throws InterruptedException {
        firstSem.acquire();
        printFirst.run();
        secondSem.release();
        // printFirst.run() outputs "first". Do not change or remove this line.
    }

    public void second(Runnable printSecond) throws InterruptedException {
        secondSem.acquire();
        printSecond.run();
        thirdSem.release();
        // printSecond.run() outputs "second". Do not change or remove this line.
        // printSecond.run();
    }

    public void third(Runnable printThird) throws InterruptedException {
        thirdSem.acquire();
        printThird.run();
        firstSem.release();
    }
}
```

{% endtab %}

{% tab title="CompletableFuture" %}

```java
class Foo {
  private volatile CompletableFuture<Void> firstFuture = CompletableFuture.completedFuture(null);
    private volatile CompletableFuture<Void> secondFuture = new CompletableFuture<>();
    private volatile CompletableFuture<Void> thirdFuture = new CompletableFuture<>();

    public void first(Runnable printFirst) throws InterruptedException {
        try {
            firstFuture.get();
            printFirst.run();
            secondFuture.complete(null);
        } catch (ExecutionException e) {
            Thread.currentThread().interrupt();
        }
    }

    public void second(Runnable printSecond) throws InterruptedException {
        try {
            secondFuture.get();
            printSecond.run();
            thirdFuture.complete(null);
        } catch (ExecutionException e) {
            Thread.currentThread().interrupt();
        }
    }

    public void third(Runnable printThird) throws InterruptedException {
        try {
            thirdFuture.get();
            printThird.run();
            // Reset for next cycle
            firstFuture = CompletableFuture.completedFuture(null);
            secondFuture = new CompletableFuture<>();
            thirdFuture = new CompletableFuture<>();
        } catch (ExecutionException e) {
            Thread.currentThread().interrupt();
        }
    }
}
```

{% endtab %}
{% endtabs %}

#### [1188. Design Bounded Blocking Queue](https://leetcode.com/problems/design-bounded-blocking-queue/)

```java
    Deque<Integer> d;
    int size;
    Object lock;
    public BoundedBlockingQueue(int capacity) {
        d = new LinkedList<>();
        size = capacity;
        lock = new Object();
    }
    
    public void enqueue(int element) throws InterruptedException {
        synchronized (lock) {
            while (d.size() == size) {
                lock.wait();
            }
            d.addLast(element);
            lock.notify();
        }
    }
    
    public int dequeue() throws InterruptedException {
        int val = 0;
        synchronized (lock) {
            while (d.isEmpty()) lock.wait();
            val = d.removeFirst();
            lock.notify();
        }
        return val;
    }
    
    public int size() {
        return d.size();
    }
```

#### [1115. Print FooBar Alternately](https://leetcode.com/problems/print-foobar-alternately/)

{% tabs %}
{% tab title="ReentrantLock" %}

```java
class FooBar {
    private int n;
    private final ReentrantLock lock = new ReentrantLock();
    private final Condition condition = lock.newCondition();
    private volatile boolean fooTurn = true;
    public FooBar(int n) {
        this.n = n;
    }

    public void foo(Runnable printFoo) throws InterruptedException {
        
        for (int i = 0; i < n; i++) {
            lock.lock();
            try {
                while (!fooTurn) {
                    condition.await();
                }
                printFoo.run();
                condition.signalAll();
                fooTurn = false;
            } finally {
                lock.unlock();
            }
        }
    }

    public void bar(Runnable printBar) throws InterruptedException {
        
        for (int i = 0; i < n; i++) {
            lock.lock();
            try {
                while (fooTurn) {
                    condition.await();
                }
                printBar.run();
                condition.signalAll();
                fooTurn = true;
            } finally {
                lock.unlock();
            }
        }
    }
}
```

{% endtab %}

{% tab title="Synchronized" %}

```java
class FooBar {
    private int n;
    private volatile boolean fooTurn = true;
    public FooBar(int n) {
        this.n = n;
    }

    public void foo(Runnable printFoo) throws InterruptedException {
        synchronized (this) {
            for (int i = 0; i < n; i++) {
                while (!fooTurn) {
                    this.wait();
                }
                // printFoo.run() outputs "foo". Do not change or remove this line.
                printFoo.run();
                fooTurn = false;
                this.notify();
            }
        }
    }

    public void bar(Runnable printBar) throws InterruptedException {
        synchronized (this) {
            for (int i = 0; i < n; i++) {
                while (fooTurn) {
                    this.wait();
                }   
                // printBar.run() outputs "bar". Do not change or remove this line.
                printBar.run();
                fooTurn = true;
                this.notify();
            }
        }
    }
}
```

{% endtab %}

{% tab title="Semaphore" %}

```java
class FooBar {
    private int n;
    private Semaphore fooSema = new Semaphore(1);
    private Semaphore barSema = new Semaphore(0);
    public FooBar(int n) {
        this.n = n;
    }

    public void foo(Runnable printFoo) throws InterruptedException {
        
        for (int i = 0; i < n; i++) {
            fooSema.acquire();
        	printFoo.run();
            barSema.release();
        }
    }

    public void bar(Runnable printBar) throws InterruptedException {
        
        for (int i = 0; i < n; i++) {
            barSema.acquire();
        	printBar.run();
            fooSema.release();
        }
    }
}
```

{% endtab %}
{% endtabs %}

**Web Crawler with future**

{% tabs %}
{% tab title="First Tab" %}

```java
import java.util.*;
import java.util.concurrent.*;

public interface WebsiteScanner {
    /**
     * Makes a call-out to the webpage and scrapes the HTML
     * for any hyperlinks to other URLs present in the webpage.
     *
     * @param url The URL of the webpage to scrape
     * @return A list of URLs (as strings) that are directly linked from the given webpage
     */
    List<String> getLinks(String url);
}

class Solution {

    /**
     * Finds the minimum number of hops required to navigate from webpageStart to webpageEnd.
     *
     * @param webpageStart The starting webpage URL
     * @param webpageEnd The target webpage URL
     * @param maxHops The maximum number of hops allowed
     * @param scanner WebsiteScanner instance to fetch links
     * @return The minimum number of hops, or -1 if not reachable within maxHops
     */
    public static int findMinimumHops(String webpageStart, String webpageEnd, int maxHops, WebsiteScanner scanner) {
        Set<String> visited = ConcurrentHashMap.newKeySet();
        Queue<String> queue = new LinkedList<>();
        ExecutorService executor = Executors.newFixedThreadPool(10);

        queue.offer(webpageStart);
        visited.add(webpageStart);
        int level = 0;

        while (!queue.isEmpty()) {
            int size = queue.size();
            List<Future<List<String>>> futures = new ArrayList<>();

            for (int i = 0; i < size; i++) {
                String current = queue.poll();
                if (current.equals(webpageEnd)) {
                    executor.shutdown();
                    return level;
                }

                futures.add(executor.submit(() -> scanner.getLinks(current)));
            }

            for (Future<List<String>> future : futures) {
                try {
                    for (String next : future.get()) {
                        if (visited.add(next)) {
                            queue.offer(next);
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

            if (++level > maxHops) {
                executor.shutdown();
                return -1;
            }
        }

        executor.shutdown();
        return -1;
    }
}

```

{% endtab %}

{% tab title="Second Tab" %}

```java
import java.util.*;
import java.util.concurrent.*;

public class ConcurrentWebCrawler {

    public interface WebsiteScanner {
        List<String> getLinks(String url);
    }

    public static void crawl(String startUrl, WebsiteScanner scanner, int maxDepth) throws InterruptedException {
        Set<String> visited = ConcurrentHashMap.newKeySet(); // thread-safe visited set
        BlockingQueue<String> queue = new LinkedBlockingQueue<>();
        ExecutorService executor = Executors.newFixedThreadPool(10);

        visited.add(startUrl);
        queue.add(startUrl);

        for (int depth = 0; depth <= maxDepth; depth++) {
            int size = queue.size();
            if (size == 0) break;

            List<Future<List<String>>> tasks = new ArrayList<>();
            for (int i = 0; i < size; i++) {
                String currentUrl = queue.poll();
                if (currentUrl == null) continue;

                String urlToCrawl = currentUrl;
                tasks.add(executor.submit(() -> scanner.getLinks(urlToCrawl)));
            }

            for (Future<List<String>> task : tasks) {
                try {
                    for (String link : task.get()) {
                        if (visited.add(link)) { // only if newly added
                            queue.add(link);
                        }
                    }
                } catch (ExecutionException e) {
                    System.err.println("Error crawling: " + e.getMessage());
                }
            }
        }

        executor.shutdown();
        executor.awaitTermination(5, TimeUnit.MINUTES);

        System.out.println("Crawled URLs:");
        for (String url : visited) {
            System.out.println(url);
        }
    }
}

```

{% endtab %}
{% endtabs %}
