[Java concurrent tool class-collaboration] CountDownLatch and CyclicBarrier


The above articles mainly introduce the mutually exclusive tool classes provided in the Java SDK: managed synchronized, Lock, ReadWriteLock, StampedLock, LockSupport. Below we will divide into several articles to introduce the tool classes that provide thread collaboration in the Java SDK. Today we will explain two tool classes, CountDownLatch and CyclicBarrier.

The following introduces a scene, the full text introduces these two tool classes around this scene

Business Introduction

Insert picture description here

1. Create thread optimization business

while(存在未对账订单){
  // 查询未对账订单
  Thread T1 = new Thread(()->{	pos = getPOrders();	});
  T1.start();
  // 查询派送单
  Thread T2 = new Thread(()->{ dos = getDOrders();	});
  T2.start();
  // 等待T1、T2结束
  T1.join();
  T2.join();
  // 执行对账操作
  diff = check(pos, dos);
  // 差异写入差异库
  save(diff);
} 

The main main thread calls the join method of threads T1 and T2 to wait for the thread to finish and exit, and then performs the reconciliation and write operations to the library.

2. The relationship between java threads and operating system threads:

The threads implemented by the operating system are extremely stable, so java thread scheduling is handed over to the operating system, and the creation, destruction, scheduling, and maintenance of threads are all implemented by the operating system (more precisely, the kernel). You only need to use threads, and do not need to design your own thread scheduling algorithm and thread preemption of CPU resources.

3. Thread pool to achieve business

Above we know that the creation and destruction of threads are heavyweight, so we often use thread pools in our development to reuse threads to avoid the creation and destruction of threads.
Below we use the thread pool to achieve the above business:

// 创建2个线程的线程池
Executor executor = Executors.newFixedThreadPool(2);
while(存在未对账订单){
  // 查询未对账订单
  executor.execute(()-> { pos = getPOrders(); });
  // 查询派送单
  executor.execute(()-> {	dos = getDOrders();});
  
  /* ??如何实现等待??*/
  
  // 执行对账操作
  diff = check(pos, dos);
  // 差异写入差异库
  save(diff);
}   

Because the threads in the thread pool will not exit, the Thread join method is invalid, what should I do? Use the CountDownLatch of the Java SDK toolkit.

4 CountDownLatch

4.1 CountDownLatch applicable scenarios

Applicable scenarios: CountDownLatch is mainly used to solve the scenario where one thread waits for multiple threads.

4.2 Using CountDownLatch to achieve thread waiting

// 创建2个线程的线程池
Executor executor = Executors.newFixedThreadPool(2);
while(存在未对账订单){
  // 计数器初始化为2
  CountDownLatch latch = new CountDownLatch(2);
  // 查询未对账订单
  executor.execute(()-> { 	pos = getPOrders();		latch.countDown();		});
  // 查询派送单
  executor.execute(()-> {	dos = getDOrders();		latch.countDown();		});
 
  latch.await(); // 等待两个查询操作结束,实现对计数器=0的等待。
  diff = check(pos, dos); // 执行对账操作
  save(diff); // 差异写入差异库
}

First, we created a CountDownLatch, the initial value of the counter is 2, when the getPOrders method is executed, call latch.countDown (), counter-1, when the counter = 0, it will wake up and execute latch.await () to continue down.

5. Further optimize performance

In the above, we open two threads to execute query unreconciled orders and query delivery orders, and then wait for the query to end before performing reconciliation operations.

In fact, there is still a point of optimization, that is, when performing a reconciliation operation, you can simultaneously query unreconciled orders and query delivery orders.
Insert picture description here
The above picture can express our intention: when both thread T1 and thread T2 have produced a piece of data (the two need to wait for each other), they can continue to go down (querying orders and sending orders), and execute thread 3 to perform reconciliation .

In fact, it is the producer-consumer model. Threads T1 and T2 both produce a piece of data and put it in a queue, and consumer T3 consumes it.

There are two difficulties in implementation:

  1. Threads T1 and T2 both produce a piece of data to execute the next round of queries.
  2. On the other hand, it is necessary to inform T3.
    Because CountDownLatch will not reset the counter after = 0, the following uses CyclicBarrier to achieve this operation.

6.CyclicBarrier

6.1 CyclicBarrier applicable scenarios

Applicable scenarios: CyclicBarrier is a group of threads waiting for each other.

In addition, the counter of CountDownLatch cannot be recycled, that is to say, once the counter decreases to 0, and then a thread calls await (), the thread will pass directly. However, the counter of CyclicBarrier can be recycled. When the counter = 0, the callback function is called to reset the counter value.

6.2 Using CyclicBarrier to achieve thread synchronization (collaboration)

Thread synchronization: is to control the cooperation between threads. The following uses CyclicBarrier to achieve the above optimization.

// 订单队列
Vector<P> pos;
// 派送单队列
Vector<D> dos;
// 执行回调的线程池 
Executor executor = Executors.newFixedThreadPool(1);//注意这里线程池中只有一个线程
final CyclicBarrier barrier =  new CyclicBarrier(2, ()->{
	executor.execute(()->check());	}); //注意这里使用的是线程池的方式。 
void check(){
  P p = pos.remove(0);
  D d = dos.remove(0);
  // 执行对账操作
  diff = check(p, d);
  // 差异写入差异库
  save(diff);
}
  
void checkAll(){
  // 循环查询订单库
  Thread T1 = new Thread(()->{
    while(存在未对账订单){  
      pos.add(getPOrders());  // 查询订单库
      barrier.await(); // 等待
    }
  });
  T1.start();  
  // 循环查询运单库
  Thread T2 = new Thread(()->{
    while(存在未对账订单){
           dos.add(getDOrders()); // 查询运单库
      barrier.await(); // 等待
    }
  });
  T2.start();
}
  1. Create CyclicBarrier, the initial value of the counter is 2, execute the callback function check () method when = 0.
  2. When the checkall () method is executed, two threads are started to query the order and delivery order respectively, the query ends, and the counter is -1.
  3. When the counter = 0, the thread in the thread pool is used to execute the check () method, and the counter is reset at the same time to start the next round of query orders and delivery orders.

Three issues:

  • In which thread is the callback function of CyclicBarrier executed?
    The callback function of CyclicBarrier is executed on the thread that finally executes barrier.await () in a round, and the check () method is executed synchronously. After the check () method is called, the next round can be started. So if check () starts another thread for asynchronous execution, it will not play a role in performance optimization.
  • Why use a thread pool instead of calling it directly in the callback function?
    I believe you almost understood through the last question, right is for asynchronous.
  • Why use single-threaded thread pool?
    The number of threads is fixed at 1, which prevents data inconsistency caused by multi-thread concurrency, because orders and delivery orders are two queues, and only if a single thread goes to the two queues to fetch messages will there be no message mismatch. (In fact, you can encapsulate the two data of each round into one data and put it in a queue. Multi-thread fetching will not have this problem.)

Reference: Geek Time
More: Deng Xin

Published 34 original articles · Likes0 · Visits 1089

Guess you like

Origin blog.csdn.net/qq_42634696/article/details/105135835