Control concurrent processes

Posted by JackOfBlades on Thu, 30 Jan 2020 05:13:25 +0100

1 what is controlling concurrent processes?

  • The tool class that controls concurrent processes is used to help our programmers make it easier for threads to cooperate
  • Let threads cooperate with each other to meet business logic
  • For example, let thread A wait for thread B to finish executing before executing cooperation strategies

What tool classes control concurrent processes?

Countdownlatch

2.1 function of countdownlatch class

Tools for concurrent process control

  • Reciprocal latch
  • Example: shopping group; bus (Park Roller Coaster queue), full departure.
  • Process: the thread is waiting until the countdown ends.

  • CountDownLatch(int count): there is only one constructor, and the parameter count is the value to be counted down.
  • Await(): the thread calling the await() method is suspended and waits until the count value is 0.
  • countDown(): reduce the count value by 1 until it is 0, and the waiting thread will be called up.

2.2 two typical uses

  • Two typical usages: first class many and many class one
  • When the CountDownLatch class creates an instance, it needs to pass the count down. When the count down to 0, the previously waiting thread will continue to run
  • CountDownLatch cannot roll back reset

First class

  • Usage 1: a thread waits for multiple threads to finish executing before continuing its work.
/**
 * Factory, quality inspection, 5 workers inspection, all people think pass, just pass
 */
public class CountDownLatchDemo1 {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(5);
        ExecutorService service = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 5; i++) {
            final int no = i + 1;
            Runnable runnable = () -> {
                try {
                    Thread.sleep((long) (Math.random() * 10000));
                    System.out.println("No." + no + "Inspection completed.");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    //Who subtracts one? Who calls countDown
                    latch.countDown();
                }
            };
            service.submit(runnable);
        }
        System.out.println("Wait for 5 people to check.....");
        //Who waits and who calls await
        latch.await();
        System.out.println("Everyone has finished their work and moved on to the next step.");
    }
}

One more.

  • Usage 2: multiple threads wait for the signal of a thread and start execution at the same time.
/**
 * Simulation 100 meters running, 5 players are ready, only wait for the referee to order, all the people start running at the same time.
 */
public class CountDownLatchDemo2 {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch begin = new CountDownLatch(1);
        ExecutorService service = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 5; i++) {
            final int no = i + 1;
            Runnable runnable = () -> {
                System.out.println("No." + no + "Ready for the gun");
                try {
                    begin.await();
                    System.out.println("No." + no + "I'm running");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            };
            service.submit(runnable);
        }
        //The referee checks the starting gun
        Thread.sleep(5000);
        System.out.println("Let's start the game!");
        //Referee shot
        begin.countDown();
    }
}

Comprehensive usage 1 and usage 2: athlete running

/**
 * Simulation 100 meters running, 5 players are ready, only wait for the referee to order, all the people start running at the same time. When everyone is at the end, the game is over.
 */
public class CountDownLatchDemo1And2 {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch begin = new CountDownLatch(1);
        CountDownLatch end = new CountDownLatch(5);
        ExecutorService service = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 5; i++) {
            final int no = i + 1;
            Runnable runnable = () -> {
                System.out.println("No." + no + "Ready for the gun");
                try {
                    begin.await();
                    System.out.println("No." + no + "I'm running");
                    Thread.sleep((long) (Math.random() * 10000));
                    System.out.println("No." + no + "It's the end");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    end.countDown();
                }
            };
            service.submit(runnable);
        }
        //The referee checks the starting gun
        Thread.sleep(5000);
        System.out.println("Let's start the game!");
        //Firing gun
        begin.countDown();
        //Waiting for all athletes to reach the finish line
        end.await();
        System.out.println("Everyone's at the end of the game");
        service.shutdown();
    }
}

2.3 precautions

  • Extended usage: multiple threads can execute at the same time after the execution of multiple threads
  • CountDownLatch cannot be reused. If you need to count again, you can consider using the CyclicBarrier or creating a new CountDownLatch instance.

Semaphore semaphore

  • Semaphore can be used to limit or manage the use of a limited number of resources.
  • Not too much pollution, only 3 pollution permits can be issued
  • The function of semaphore is to maintain a "license" count. If a thread can "acquire" a license, the remaining license of semaphore will be reduced by one, and the thread can "release" a license, and the remaining license of semaphore will be increased by one. When the number of licenses owned by semaphore is 0, the next thread that wants to acquire a license will need to wait, Until another thread releases the license

 

When semaphores are not used, slow service will drag down the whole program

3.1 using semaphores

Semaphore usage process

  • Initialize Semaphore and specify the number of licenses
  • Add the acquire() or acquisunnterrruptible () method before the current code
  • At the end of the task, release() is called to release the license

 

 

3.2 introduction to main methods of semaphore

  • New Semaphore (int limits, Boolean Fair): you can set whether to use the fair policy here. If you pass in true, Semaphore will put the previously waiting threads in the FIFO queue, so that when you have a new license, it can be distributed to the threads that have been waiting for the longest time.
  • acquire(): able to respond to interrupt
  • Acquirenterruptibly(): unable to respond to interrupt
  • tryAcquire(): see if there is a free license now. If there is one, get it. If not, it doesn't matter. I don't have to get stuck. I can do something else. I'll check the free status of the license later.
  • ◆ try acquire (long timeout, timeunit unit): the same as try acquire(), but there is an extra timeout, such as "if I can't get the license in 3 seconds, I will do something else"
  • release(): return license

3.3 semaphore code demonstration

/**
 * Demonstrate Semaphore usage
 */
public class SemaphoreDemo {
    static Semaphore semaphore = new Semaphore(5, true);
    public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(50);
        for (int i = 0; i < 10; i++) {
            service.submit(new Task());
        }
        service.shutdown();
    }

    static class Task implements Runnable {
        @Override
        public void run() {
            try {
                //!!! Get and release need to be consistent
                semaphore.acquire(3);//semaphore.release(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "Got a license");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "License released");
            semaphore.release(3);
        }
    }
}

3.4 special usage of semaphore

Acquire or release multiple licenses at once

  • For example, TaskA will call method1(), which consumes A lot of resources, while TaskB will call method2(), which does not consume A lot of resources. Suppose we have five licenses in total. Then we can ask TaskA to obtain five licenses to execute, and TaskB only needs to obtain one license to execute, so as to avoid the situation of simultaneous operation of A and B. we can reasonably allocate resources according to our own needs.

3.5 precautions

  1. The number of licenses obtained and released must be the same. Otherwise, for example, two licenses are obtained each time but only one license is released or not released. As time goes on, when the number of licenses is insufficient, the program will die. (although semaphore classes do not specify whether or not and the number of acquisitions, this is our programming specification, otherwise it is prone to error.).
  2. Pay attention to setting fairness when initializing Semaphore. Generally, it is more reasonable to set true.
  3. It is not necessary to release the license by the thread obtaining the license. In fact, acquiring and releasing the license does not require the thread. Maybe A obtains the license, and then B releases the license, as long as the logic is reasonable.
  4. The function of semaphore is not only to control the critical area and have N threads at most at the same time, but also to realize "conditional waiting". For example, thread 1 needs to start work after thread 2 completes the preparation work. Then, thread 1 acquire() and thread 2 (execute first) completes the task and then release(). In this case, it is a lightweight CountDownLatch.

4 Condition interface (also known as condition object)

4.1 role

  • When thread 1 needs to wait for a certain condition, it will execute the condition.await() method. Once the await() method is executed, the thread will enter a blocking state.
  • Then there will be another thread, assuming that thread 2 will execute the corresponding condition. Until the condition is reached, thread 2 will execute the condition.signal() method, and the JVM will find the thread waiting for the condition from the blocked thread. When thread 1 receives the executable signal, its thread status will become Runnable executable Status.

Difference between signalAll() and signal()

  • signalAll() calls up all waiting threads
  • But signal() is fair. It only calls up the thread with the longest waiting time

4.2 code demonstration

Common example

/**
 * Demonstrate the basic usage of Condition
 */
public class ConditionDemo1 {
    private ReentrantLock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();
    void method1() throws InterruptedException {
        lock.lock();
        try{
            System.out.println("Conditions not met, start await");
            condition.await();
            System.out.println("If the conditions are met, start to perform the following tasks");
        }finally {
            lock.unlock();
        }
    }
    void method2() {
        lock.lock();
        try{
            System.out.println("The preparations are completed, and other threads are awakened");
            condition.signal();
        }finally {
            lock.unlock();
        }
    }
    public static void main(String[] args) throws InterruptedException {
        ConditionDemo1 conditionDemo1 = new ConditionDemo1();
        new Thread(() -> {
            try {
                Thread.sleep(1000);
                conditionDemo1.method2();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        conditionDemo1.method1();
    }
}

Using Condition to realize producer consumer pattern

/**
 * Demonstrate the implementation of producer consumer model with Condition
 */
public class ConditionDemo2 {
    private final int QUEUE_SIZE = 10;
    private PriorityQueue<Integer> queue = new PriorityQueue<>(QUEUE_SIZE);
    private Lock lock = new ReentrantLock();
    //Not full (for producers)
    private Condition notFull = lock.newCondition();
    //No space (consumer)
    private Condition notEmpty = lock.newCondition();
    public static void main(String[] args) {
        ConditionDemo2 conditionDemo2 = new ConditionDemo2();
        Producer producer = conditionDemo2.new Producer();
        Consumer consumer = conditionDemo2.new Consumer();
        new Thread(producer).start();
        new Thread(consumer).start();
    }
    //Consumer
    class Consumer implements Runnable {
        @Override
        public void run() {
            consume();
        }
        private void consume() {
            while (true) {
                lock.lock();
                try {
                    while (queue.size() == 0) {
                        System.out.println("Queue empty, waiting for data");
                        try {
                            notEmpty.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    queue.poll();
                    //Wake up producers
                    notFull.signalAll();
                    System.out.println("A data is taken from the queue, and the queue is left" + queue.size() + "Element");
                } finally {
                    lock.unlock();
                }
            }
        }
    }
    //Producer
    class Producer implements Runnable {
        @Override
        public void run() {
            produce();
        }
        private void produce() {
            while (true) {
                lock.lock();
                try {
                    while (queue.size() == QUEUE_SIZE) {
                        System.out.println("Queue full, waiting for free");
                        try {
                            notFull.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    queue.offer(1);
                    //Wake up consumers
                    notEmpty.signalAll();
                    System.out.println("An element was inserted into the queue, leaving space for the queue" + (QUEUE_SIZE - queue.size()));
                } finally {
                    lock.unlock();
                }
            }
        }
    }
}

4.3 conditions

  • In fact, if Lock is used to replace synchronized, Condition is used to replace the corresponding Object.wait/notify, so it is almost the same in usage and nature.
  • The await method will automatically release the Lock held. Like Object.wait, it does not need to manually release the Lock first.
  • When await is called, the lock must be held, or an exception will be thrown, just like Object.wait.

5 cyclic barrier

5.1 using CyclicBarrier

  • Similar to CountDownLatch, CyclicBarrier can block group threads
  • When a large number of threads cooperate with each other, calculate different tasks respectively, and need to summarize uniformly at last, we can use CyclicBarrier. CyclicBarrier can construct an assembly point. When a thread finishes executing, it will wait at the assembly point until all threads have reached the assembly point. Then the fence will be revoked, and all threads will set out in a unified way to continue to perform the remaining tasks.
  • Example in life: "three of us will meet at school at noon tomorrow. When we all arrive, we will discuss the plan for next semester together."
/**
 * Demonstrate CyclicBarrier
 */
public class CyclicBarrierDemo {
    public static void main(String[] args) {
        //5 one departure
        CyclicBarrier cyclicBarrier = new CyclicBarrier(5, new Runnable() {
            @Override
            public void run() {
                System.out.println("It's full. Let's go!");
            }
        });
        //10: Two trains
        //5: Send a train
        for (int i = 0; i < 10; i++) {
            new Thread(new Task(i, cyclicBarrier)).start();
        }
    }
    static class Task implements Runnable{
        private int id;
        private CyclicBarrier cyclicBarrier;

        public Task(int id, CyclicBarrier cyclicBarrier) {
            this.id = id;
            this.cyclicBarrier = cyclicBarrier;
        }
        @Override
        public void run() {
            System.out.println("thread" + id + "Now go to the assembly point");
            try {
                Thread.sleep((long) (Math.random()*10000));
                System.out.println("thread"+id+"When we got to the assembly point, we began to wait for others to arrive");
                cyclicBarrier.await();
                System.out.println("thread"+id+"Here we go");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        }
    }
}

5.2 difference between cyclicbarrier and countdownload

  • Different functions: CyclicBarrier needs to wait for a fixed number of threads to reach the fence position to continue execution, while CountDownLatch only needs to wait for the number to 0, that is to say, CountDownLatch is used for events, but CyclicBarrier is used for threads.
  • Different reusability: CountDownLatch cannot be used again after counting down to 0 and triggering the latch to open, unless a new instance is created. CyclicBarrier can be reused.

 

517 original articles published, 454 praised, 1.09 million visitors+
His message board follow

Topics: Programming jvm