leetcode concurrent problem-solving report JAVA version

Keywords: Java JDK

Print in Order
Suppose we have a class:

public class Foo {
  public void first() { print("first"); }
  public void second() { print("second"); }
  public void third() { print("third"); }
}

The same instance of Foo will be passed to three different threads. Thread A will call first(), thread B will call second(), and thread C will call third(). Design a mechanism and modify the program to ensure that second() is executed after first(), and third() is executed after second().

Example 1:
Input: [1,2,3]
Output: "firstsecondthird"
Explanation: There are three threads being fired asynchronously. The input [1,2,3] means thread A calls first(), thread B calls second(), and thread C calls third(). "firstsecondthird" is the correct output.

Method 1: Semaphore, semaphore and mutex are all kernel objects, which can be used for inter-process synchronization and occupy system resources. The difference is that mutex can only access protected resources by one thread. Semaphore is a counted mutex lock that defines the number of threads accessing protected resources simultaneously

import java.util.concurrent.Semaphore;
class Foo {
private static Semaphore firstSemaphore = new Semaphore(1);
private static Semaphore secordSemaphore = new Semaphore(0);
private static Semaphore thirdSemaphore = new Semaphore(0);
public Foo() {

}
public void first(Runnable printFirst) throws InterruptedException {
    firstSemaphore.acquire();
    // printFirst.run() outputs "first". Do not change or remove this line.
    printFirst.run();
    secordSemaphore.release();
}
public void second(Runnable printSecond) throws InterruptedException {
    secordSemaphore.acquire();
    // printSecond.run() outputs "second". Do not change or remove this line.
    printSecond.run();
    thirdSemaphore.release();
}
public void third(Runnable printThird) throws InterruptedException {
    thirdSemaphore.acquire();
    // printThird.run() outputs "third". Do not change or remove this line.
    printThird.run();
    firstSemaphore.release();
}

}
Method 2. Atomic Integer, an atomic class, is an Integer class that provides atomic operations, adding and subtracting operations in a thread-safe manner.

import java.util.concurrent.atomic.AtomicInteger;
class Foo {

private final AtomicInteger count = new AtomicInteger(0);
public Foo() {

}
public void first(Runnable printFirst) throws InterruptedException {
    //Spin, avoid entering the kernel state, but waste CPU resources
    while (count.get() != 0) {}
    printFirst.run();
    count.incrementAndGet();
}
public void second(Runnable printSecond) throws InterruptedException {
    while (count.get() != 1) {}
    printSecond.run();
    count.incrementAndGet();
}
public void third(Runnable printThird) throws InterruptedException {
    while (count.get() != 2) {}
    printThird.run();
    count.set(0);
}

}
Method 3: CountDownLatch, reference method 2. CountDownLatch allows us to easily implement the following requirement when multithreaded collaboration completes business functions. After waiting for multiple threads to complete tasks, the main thread can continue to perform business functions.

Print FooBar Alternately
Suppose you are given the following code:

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");
    }
  }
}

The same instance of FooBar will be passed to two different threads. Thread A will call foo()while thread B will call bar(). Modify the given program to output "foobar" n times.

Example 2:
Input: n = 2
Output: "foobarfoobar"
Explanation: "foobar" is being output 2 times.

This problem is actually an upgraded version of Title 1. It can also be easily solved by using signal quantities. Here are some other solutions.

Thread problem is to ensure consistency, orderliness and avoid deadlock. Visibility can be guaranteed by volatile. Orderliness can be guaranteed by lock. Deadlock problem can break the condition of deadlock. That is to say, we need to apply for resources in batches or acquire all resources in sequence before we can acquire the lock, such as

class FooBar {
private int n;

private List<String> als;

public FooBar(int n) {
    this.n = n;
    als = new ArrayList<>(2);
}

synchronized void  apply(String pre,String next) {
    while (als.contains(pre)||als.contains(next)) {
        try {
            wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    als.add(pre);
    als.add(next);
}

synchronized void  free(String pre,String next) {
    als.remove(pre);
    als.remove(next);
    notifyAll();
}

public void foo(Runnable printFoo) throws InterruptedException {

    for (int i = 0; i < n; i++) {
            apply("bar","foo");
            // printFoo.run() outputs "foo". Do not change or remove this line.
            printFoo.run();
            free("foo","bar");

    }
}

public void bar(Runnable printBar) throws InterruptedException {

    for (int i = 0; i < n; i++) {
        apply("foo","bar");
        // printBar.run() outputs "bar". Do not change or remove this line.
        printBar.run();
        free("bar","foo");
    }
}

}
But the above examples can not meet the requirements of this topic. When n=1, the output may be foobar or barfoo. Similarly, when n=k, there will also be sequential problems. It seems to be solved by add ing and removing string order, but it has not achieved the effect. The specific analysis process is left to the reader to complete.

We do it in a different way, using the standard production-consumption model.

class FooBar {
private int n;
private Object lock;
private Boolean printFooStatus;
public FooBar(int n) {
    this.n = n;
    lock = new Object();
    printFooStatus = true;
}

public void foo(Runnable printFoo) throws InterruptedException {
    for (int i = 0; i < n; i++) {
          synchronized (lock) {
              //Must use while
              while (!printFooStatus){
                  lock.wait();
              }
              // printFoo.run() outputs "foo". Do not change or remove this line.
              printFoo.run();
              printFooStatus = false;
               //Must be placed in synchronized
              lock.notifyAll();
          }
    }
}
public void bar(Runnable printBar) throws InterruptedException {
    for (int i = 0; i < n; i++) {
        synchronized (lock) {
            //Must use while
            while (printFooStatus) {
                lock.wait();
            }
            // printBar.run() outputs "bar". Do not change or remove this line.
            printBar.run();
            printFooStatus = true;
            //Must be placed in synchronized
            lock.notifyAll();
        }
    }
}
}

Here are some points to be noted:

1. Beginners understand wait() as blocking the current thread, so Thread. current Thread (). wait(); seems reasonable. But I don't know if you have found that wait() and notify() methods in JDK class libraries are not Thread classes, but Object(). The current thread waits before other threads call the notify() method or notifyAll() method of this object
2. Always use the while loop to call the wait method and never call the wait method outside the loop. The reason for this is that although the condition is not satisfied, the call of notifyAll method by other threads will cause the blocked thread to wake up unexpectedly. When the execution condition is not satisfied, it will cause the constraint to fail.
3. Wake-up threads should use notify or notify All? Notify will randomly notify a thread in the waiting queue, while notify All will notify all threads in the waiting queue. It is known that notify is risky and may cause some threads never to be notified.
4. The current thread must own the object monitor before it can relinquish its ownership of the monitor and wait until other threads call the notify method or notify All method to notify the thread waiting on the monitor of the object to wake up, and then the thread will wait until it regains ownership of the monitor. Continued implementation. Otherwise, an Illegal Monitor State Exception error will be reported.

However, Object's waitnotifynotifyAll principle is based on the object Monitor in the kernel, which may lead to a large number of context switching. For better performance, ConditionObject, a member variable in the display lock ReetrantLock based on AQS, is often used instead. There is a synchronization queue in AQS. When a thread does not acquire a lock, it will enter the synchronization queue and block. If it is acquired after waking up, it will move out of the synchronization queue. In addition, there is a conditional queue in AQS. Through the addWaiter method, the thread invoked by the wait() method can be placed in the conditional queue. The thread enters the waiting state. When the signal or signalAll method is invoked, the thread will be awakened and then enter the synchronous queue. Conditional queues are implemented through linked lists, so multiple waiting queues can be supported. That is to say, the principle of awaitsignalAll based on AQS interface is implemented on JAVA code layer, which has more advantages in performance.

So another way to write this question is

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
class FooBar {
private int n;
private Boolean printFooStatus;
private ReentrantLock reentrantLock;
private Condition condition;
public FooBar(int n) {
    this.n = n;
    printFooStatus = true;
    //Whether fair or unfair locks are used, there is no difference in this question.
    reentrantLock= new ReentrantLock(false);
    condition = reentrantLock.newCondition();
}
public void foo(Runnable printFoo) throws InterruptedException {
    for (int i = 0; i < n; i++) {
        reentrantLock.lock();
        try {
            while (!printFooStatus){
                condition.await();
            }
            // printFoo.run() outputs "foo". Do not change or remove this line.
            printFoo.run();
        } catch (Exception e) {
           e.printStackTrace();
        } finally {
            printFooStatus = false;
            //Similarly: must be placed in the lock
             condition.signalAll();
            reentrantLock.unlock();

        }
    }
}
public void bar(Runnable printBar) throws InterruptedException {
    for (int i = 0; i < n; i++) {
      reentrantLock.lock();
      try {
          while (printFooStatus){
              condition.await();
          }
          // printBar.run() outputs "bar". Do not change or remove this line.
          printBar.run();
      } catch (Exception e) {
         e.printStackTrace();
      } finally {
          printFooStatus = true; 
          //Similarly: must be placed in the lock
           condition.signalAll();
          reentrantLock.unlock();

      }
    }
}

}
Print Zero Even Odd
Suppose you are given the following code:

class ZeroEvenOdd {
  public ZeroEvenOdd(int n) { ... }      // constructor
  public void zero(printNumber) { ... }  // only output 0's
  public void even(printNumber) { ... }  // only output even numbers
  public void odd(printNumber) { ... }   // only output odd numbers
}

The same instance of ZeroEvenOdd will be passed to three different threads:

Thread A will call zero() which should only output 0's.
Thread B will call even() which should only ouput even numbers.
Thread C will call odd() which should only output odd numbers.
Each of the thread is given a printNumbermethod to output an integer. Modify the given program to output the series 010203040506... where the length of the series must be 2n.
Example 1:
Input: n = 2
Output: "0102"
Explanation: There are three threads being fired asynchronously. One of them calls zero(), the other calls even(), and the last one calls odd(). "0102" is the correct output.

Example 2:
Input: n = 5
Output: "0102030405"

Tip: Use Signal Quantity

Building H_2O
There are two kinds of threads, oxygen and hydrogen. Your goal is to group these threads to form water molecules. There is a barrier where each thread has to wait until a complete molecule can be formed. Hydrogen and oxygen threads will be given a releaseHydrogen and releaseOxygen method respectfully, which will allow them to pass the barrier. These threads should pass the barrier in groups of three, and they must be able to immediately bond with each other to form a water molecule. You must guarantee that all the threads from one molecule bond before any other threads from the next molecule do.

In other words:

If an oxygen thread arrives at the barrier when no hydrogen threads are present, it has to wait for two hydrogen threads.
If a hydrogen thread arrives at the barrier when no other threads are present, it has to wait for an oxygen thread and another hydrogen thread.
We don't have to worry about matching the threads up explicitly; that is, the threads do not necessarily know which other threads they are paired up with. The key is just that threads pass the barrier in complete sets; thus, if we examine the sequence of threads that bond and divide them into groups of three, each group should contain one oxygen and two hydrogen threads.

Write synchronization code for oxygen and hydrogen molecules that enforces these constraints.
Example 1:
Input: "HOH"
Output: "HHO"
Explanation: "HOH" and "OHH" are also valid answers.

class H2O {

private Object lock = new Object();
private int counter =0;
public H2O() {

}
public void hydrogen(Runnable releaseHydrogen) throws InterruptedException {

 synchronized (lock) {
        while(counter==2){
            lock.wait();
        }
        releaseHydrogen.run();
        counter++;
        lock.notifyAll();
  }
}

public void oxygen(Runnable releaseOxygen) throws InterruptedException {
    synchronized (lock) {
        while(counter!=2){
            lock.wait();
        }
        releaseOxygen.run();
        counter=0;
        lock.notifyAll();
  }

}
}

Source of the article: www.liangsonghua.me

Author's introduction: Liang Songhua, a senior engineer in Jingdong, has a deep understanding of stability assurance, agile development, advanced JAVA and micro-service architecture.

Posted by Headwaters on Wed, 31 Jul 2019 19:29:43 -0700