In-depth analysis of JUC thread framework - 03, thread synchronization auxiliary tool class

[Semaphore: Semaphore]
➣ Semaphore is usually used to limit the number of threads that can access certain resources (physical or logical).
➣ For example, everyone is queuing up to go to the bank for business, but only two bank windows provide services, and 10 people need to queue up, so the 10 people in line need to use these two business windows in turn.

  First, let's observe the basic definition form of the java.util.concurrent.Semaphore class: 
  public class Semaphore extends Object implements Serializable

  The methods defined in the Semaphore class are as follows:
     Constructor: public Semaphore(int permits) Set the number of signals for the service.
     Constructor: whether public Semaphore(int permits, Boolean fair) is a fair lock.
     Waiting for execution: public void acquireUninteruptibly(int permits)
        |- If there is a blocked thread object on the set semaphore, it will continue to be blocked;
     release the blocked state of the thread: public void release(int permits);

     Return the number of available resources: public int availablePermits();

Example: Implementing bank queuing for office processing

import java.util.Random;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

public class MLDNTestDemo {
    public static void main(String[] args) throws Exception {
        // Now there are a total of 2 resources allowed to operate
        Semaphore sem = new Semaphore (2);
        // Simulate the time for each user to do business
        Random rand = new Random();
        for (int x = 0; x < 10; x++) {
            // Each thread is a person to handle business
            new Thread(() -> {
                // now there are free windows
                if (sem.availablePermits() > 0) {
                    System.out.println("
                            【"+Thread.currentThread().getName()
+"] Enter the bank, no one is in line at the bank at this time");
                } else { // no free space
                    System.out.println("【"+Thread.currentThread()
.getName()+"] Queue for business.");
                }
                try {
                    // get permission to operate from semaphore
                    sem.acquire();
                    System.out.println("
["+Thread.currentThread().getName()+"]{start} to start business. ");
                    // Simulate office delay
                    TimeUnit.SECONDS.sleep(rand.nextInt(10));
                    System.out.println("["+Thread.currentThread().getName()+"]{end} ends the business.");
                    // The current thread leaves the office window
                    sem.release();
                } catch (InterruptedException e) {
                    e.printStackTrace ();
                }
            },"Customer-" + x).start();
        }
    }
}

[Customer-0] enters the bank, and no one is in line at the bank at this time
[Customer-3] enters the bank, at this time, there is no one in the bank
[Customer-2] Enters the bank, at this time, there is no one in the bank
[Customer-1] enters the bank, At this time, there is no one in the bank
[Customer-5] waiting in line to handle the business.
【Customer-4】Waiting in line to handle the business.
【Customer-6】Waiting in line to handle the business.
【Customer-3】{start} starts to handle business.
【Customer-7】Waiting in line to handle the business.
【Customer-0】{start} starts to handle business.
【Customer-8】Waiting in line to handle the business.
【Customer-9】Waiting in line to handle the business.
【Customer-0】{end} has ended the business.
【Customer-2】{start} starts to handle business.
【Customer-3】{end} has ended the transaction.
【Customer-1】{start} starts to handle business.
【Customer-1】{end} ends the transaction.
【Customer-5】{start} began to handle business.
【Customer-2】{end} has ended the transaction.
【Customer-4】{start} starts to handle business.
【Customer-4】{end} has ended the transaction.
【Customer-5】{end} has ended the transaction.
【Customer-6】{start} starts to handle business.
【Customer-7】{start} starts to handle business.
【Customer-7】{end} ends the business.
【Customer-8】{start} began to handle business.
【Customer-6】{end} has ended the business.
【Customer-9】{start} starts to handle business.
【Customer-8】{end} has ended the transaction.
【Customer-9】{end} has ended the transaction.

       The processing of this kind of semaphore works in actual development: (for example) there are currently 2 connections for database operation connections, so there may be 10 users waiting for database operations, and the number of connections that can be used is 2, so this 10 users need to queue up to use these two connections in turn to operate.

[Latching: CountDownLatch]
CountDownLatch describes the reduction of a technique. Let's first observe a simple problem of a program.

Example: write a simple multithreaded development

public class MLDNTestDemo {
	public static void main(String[] args) throws Exception {
		for (int x = 0; x < 2; x++) {
			new Thread(() -> {
				System.out.println("["+Thread.currentThread().getName()+"] The thread application is executed.");
			},"Thread object-" + x).start();
		}
		System.out.println("[*** Main thread ***] All programs have been executed.");
	}
}

     At this time, it should be ensured that all threads are executed before the output calculation of the program is performed. It is like: the tourist group gathers people to leave by car, so it is necessary to ensure that all threads are executed (the specified number of threads), so that A count processing must be done.

【 Latch: CountDownLatch 】



The common methods in the CountDown class are as follows:
       Constructor: public CountDownLatch(int count), to set the number of waiting threads;
       reduce the number of waiting: public void countdown();

       等待countDownLatch为0:public void await() throws InterruptedException;

Example: Using CountDown to Solve Previous Design Problems

import java.util.concurrent.CountDownLatch;
public class MLDNTestDemo {
    public static void main(String[] args) throws Exception {
        // After all 2 threads are executed, they can continue to execute
        CountDownLatch cdl = new CountDownLatch(2) ;
        for (int x = 0; x < 2; x++) {
            new Thread(() -> {
                System.out.println("【"
                        +Thread.currentThread().getName()
                        +"]The thread application is executed.");
                cdl.countDown(); // reduce the number of threads waiting
            },"Thread object-" + x).start();
        }
        cdl.await(); // wait for the end of the count (number 0)
        System.out.println("[*** Main thread ***] All programs have been executed.");
    }
}
-----------
[Thread object-0] The thread application has been executed.
[Thread object-1] The thread application is executed.
[*** Main thread***] All programs are executed.

The role of CountDown, the methods defined by the class, and the operation process may become interview questions.

【Fence: CyclicBarrier】

     CyclicBarrier and CountDownLatch are very similar. The core concept of CyclicBarrier is to set a boundary of the number of waiting threads, and execute after reaching this boundary.


The main methods of the CyclicBarrier class are as follows:
     Constructor: public CyclicBarrier(int parties), set the waiting boundary;
     silly waiting for other threads: public int await() throws InterruptedException, BrokenBarrierException;
     waiting for other threads: public int await(long timeout, TimeUnit unit) throws
                  InterruptedException, BrokenBarrierException, TimeoutException.

     Reset the number of waiting threads: public void reset().

Example: Watching CyclicBarrier for waiting
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
public class MLDNTestDemo {
    public static void main(String[] args) throws Exception {
        // Trigger when there are enough 2 threads
        CyclicBarrier cb = new CyclicBarrier(2);
        for (int x = 0; x < 3; x++) {
            int sec = x ;
            new Thread(() -> {
                System.out.println("【"
                        + Thread.currentThread().getName()
                        + " - waiting to start]");
                try {
                    TimeUnit.SECONDS.sleep(sec);
                    cb.await(); // wait for processing
                } catch (Exception e) { e.printStackTrace(); }
                System.out.println("【"
                        + Thread.currentThread().getName()
                        + " - wait for the end]");
            }, "Entertainer-" + x).start();
        }
    }
}

If you don't want to wait all the time, you can set a timeout, after which a "TimeoutException" will occur.

import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
public class MLDNTestDemo {
    public static void main(String[] args) throws Exception {
        // Trigger when there are enough 2 threads
        CyclicBarrier cb = new CyclicBarrier(2);
        for (int x = 0; x < 3; x++) {
            int sec = x ;
            new Thread(() -> {
                System.out.println("【"
                        + Thread.currentThread().getName()
                        + " - waiting to start]");
                try {
                    TimeUnit.SECONDS.sleep(sec);
                    cb.await(6,TimeUnit.SECONDS); // wait for processing
                } catch (Exception e) { e.printStackTrace();  }
                System.out.println("【"
                        + Thread.currentThread().getName()
                        + " - wait for the end]");
            }, "Entertainer-" + x).start();
        }
    }
}

Example: reset processing (reset processing possible)

import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;

public class MLDNTestDemo {
    public static void main(String[] args) throws Exception {
        // Trigger when there are enough 2 threads
        CyclicBarrier cb = new CyclicBarrier(2);
        for (int x = 0; x < 3; x++) {
            int sec = x ;
            new Thread(() -> {
                System.out.println("【" + Thread.currentThread().getName() + " - 等待开始】");
                try {
                    if (sec == 2) { // reset
                        cb.reset(); // reset
                        System.out.println("[Reset processing] ****** " +
Thread.currentThread().getName());
                    } else {
                        TimeUnit.SECONDS.sleep(sec);
                        cb.await(6,TimeUnit.SECONDS); // wait for processing
                    }
                } catch (Exception e) {
                    e.printStackTrace ();
                }
                System.out.println("【" +
Thread.currentThread().getName()
+ " - wait for the end]");
            }, "Entertainer-" + x).start();
        }
    }
}

Interview question: What is the difference between CountDownLatch and CyclicBarrier?
CountDownLatch
: The biggest feature is to wait for a data subtraction operation. Once all statistical operations are started, the countDown() method must be executed all the time.
If the number of waits is not 0, it will be waited all the time and cannot be reset;
CyclicBarrier : Set a Waiting for the critical point, and there can be multiple waiting threads. As long as the critical point is met and the execution code of the thread is triggered, the counting processing operation will be restarted, or the reset() method can be used directly to perform the reset operation.

[Exchange space: Exchanger]
      If there are two threads, one thread is responsible for producing data, and the other thread is responsible for consuming data, then there must be a common area between these two threads, then the implementation of this common area is in In the juc package, it becomes Exchanger.

➣ The java.util.concurrent.Exchanger class represents a meeting point where two threads can exchange objects with each other.


The methods defined in the Exchanger class are as follows:
      Constructor: public Exchanger(), which creates an object;

     Set and get: public V exchange(V x) throws InterruptedException.

Example: Using Exchanger to implement exchange processing

import java.util.concurrent.Exchanger;
import java.util.concurrent.TimeUnit;

public class MLDNTestDemo {
    public static void main(String[] args) throws Exception {
        // prepare a swap space
        Exchanger<String> exc = new Exchanger<String>();
        for (int x = 0; x < 3; x++) {
            new Thread(() -> {
                while (true) {
                    try {
                        String data = exc.exchange(null) ;
                        TimeUnit.SECONDS.sleep(2);
                        // Now get the producer's data
                        if (data != null) {
                            System.out.println("【" +
                   Thread.currentThread().getName() + "】" + data);
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace ();
                    }
                }
            }, "Consumer-" + x).start();
        }
        // ready to define two producers
        for (int x = 0; x < 2; x++) {
            int temp = x;
            new Thread(() -> {
                // A total of four data are produced
                for (int y = 0; y < 2; y++) {
                    String data = "MLDN - " + temp + " - " + y;
                    try {
                        // tell the producer to slow down
                        TimeUnit.SECONDS.sleep(2);
                        // save the produced data in swap space
                        exc.exchange(data);
                        System.out.println("【" +
Thread.currentThread().getName()
+ "] produced data: " + data);
                    } catch (InterruptedException e) {
                        e.printStackTrace ();
                    }
                }
            }, "Producer-" + x).start();
        }
    }
}

      For such operation processing, in the multi-threading basic part, the implementation used a large number of operations such as wait(), notify(), synchronized, etc., but the overall operation of Exchanger in JUC becomes much simpler.

[Thread callback: CompletableFuture]

      Scenario: Now to bomb a famous landmark with artillery.


All execution threads must enter the blocking state before receiving the command, and will not execute the next operation processing until the specific command is received.
➣ CompletableFuture is a class added in java8. The main function of this class is to provide a new way to complete asynchronous processing, including non-blocking ways of synthesizing and combining events.

 

The following methods are provided in the CompletableFuture class:
      Constructor: public CompletableFuture();  
     Get command: public T get() throws InterruptedException, ExecutionException;

Example: Use CompletableFuture to implement a shooting operation

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

public class MLDNTestDemo {
    public static void main(String[] args) throws Exception {
        // is to directly use the constructor to create the object of the class
        CompletableFuture<String> future =
                       new CompletableFuture<String>() ;
        for (int x = 0; x < 10; x++) {
            new Thread(() -> {
                System.out.println("BEFORE【"
                        +Thread.currentThread().getName()
                        +"] Enter the artillery position, wait for the order, and prepare to fire.");
                try {
                    String cmd = future.get() ; // receive command
                    if ("fire".equalsIgnoreCase(cmd)) {
                        System.out.println("AFTER【"
                            +Thread.currentThread().getName()
                           +"】Received the order, opened fire immediately, and killed that fat fat man." );
                    }
                    if ("cancel".equalsIgnoreCase(cmd)) {
                        System.out.println("AFTER【"
                                +Thread.currentThread().getName()
                                +"] Receive the retreat order, go home and sleep." );
                    }
                } catch (Exception e) {
                    e.printStackTrace ();
                }
            },"Artillery-" + x).start();
        }
        TimeUnit.SECONDS.sleep(3); // wait for 3 seconds
        future.complete("cancel") ; // gives the command to execute
    }
}

   The processing of this operation is mainly based on the implementation of the basic IQ of the Future thread model.
   For this class, in addition to the above usage methods, asynchronous thread execution methods can also be used for processing. When creating a CompletableFuture class object, you can also use a static method provided in this class: public static CompletableFuture<Void> runAsync(Runnable runnable).

Example: Replacing the Implementation Form

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

public class MLDNTestDemo {
    public static void main(String[] args) throws Exception {
 // If the Runnable interface object is used, the generic type must be Void, which means there is no return value
        // fire when this thread finishes executing
        CompletableFuture<Void> future =
                   CompletableFuture.runAsync(() -> {
            System.out.println("
                 【FUTURE】The general is having a sweet dream in the gentle village, waiting for the general to wake up and fire. ");
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace ();
            }
            System.out.println("[FUTURE] woke up and started working.");
        });
        for (int x = 0; x < 10; x++) {
            new Thread(() -> {
                System.out.println("BEFORE【"
                        +Thread.currentThread().getName()
                        +"] Enter the artillery position, wait for the order, and prepare to fire.");
                try {
                    System.out.println("AFTER【"
                            +Thread.currentThread().getName()
                            +"】Received the order, opened fire immediately, and killed that fat fat man. . . "
                            + future.get());
                } catch (Exception e) {
                    e.printStackTrace ();
                }
            },"Artillery-" + x).start();
        }    }  }

  The biggest benefit of this class is that it provides the execution trigger point for all waiting threads.











Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325992680&siteId=291194637