Implementing counters in Java multithreads using the JDK built-in tool class

Posted by freedomclan on Mon, 25 May 2020 18:56:38 +0200

Preface

In the actual development process, we often encounter businesses that require multi-threaded concurrency. Finally, we need to summarize the tasks completed by each thread, but the main thread usually ends earlier than the sub-threads. If you want to wait for each sub-thread to finish before running the main thread, you need to identify whether each thread is finished or not.Concurrent packages provide developers with several good use tool classes.

Next, the above case scenario will be analyzed using the Thread#join method and the CountDownLatch, CyclicBarrier classes.

Thread#join method

The child thread object using the join() method normally executes the code in run(), but the current thread will be blocked without a timeout and will continue executing the blocked current thread after the thread executing the join() method has been destroyed.The join() method blocking principle is that the wait() method is used to block within the method, and the source code is as follows:

NotfyAll() is called when the child thread join() completes to notify the current thread to continue executing the next code.

If you now have two threads that produce data results, and finally add the two threads together, if you execute and summarize the two threads directly, implement the code as follows:

package top.ytao.demo.thread.count;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * htpps://ytao.top
 * 
 * Created by YangTao on 2020/5/17 0017.
 */
public class JoinTest {


    public static void main(String[] args) throws InterruptedException {

        Map<String, Integer> map = new ConcurrentHashMap<>();

        Thread thread1 = new Thread(() -> {
            map.put("thread1", 1);
            System.out.println("run thread1");
        });

        Thread thread2 = new Thread(() -> {
            map.put("thread2", 2);
            System.out.println("run thread2");
        });


        thread1.start();
        thread2.start();

        System.out.println(map.get("thread1") + map.get("thread2"));

        System.out.println("end....");

    }
}

Execution results:

Since the main thread's summary calculation may be completed earlier than the child thread, getting the child thread results in a null pointer exception.

Block the main thread by adding a join() method and wait for the child threads to finish before summarizing:

package top.ytao.demo.thread.count;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * htpps://ytao.top
 * 
 * Created by YangTao on 2020/5/17 0017.
 */
public class JoinTest {


    public static void main(String[] args) throws InterruptedException {

        Map<String, Integer> map = new ConcurrentHashMap<>();

        Thread thread1 = new Thread(() -> {
            map.put("thread1", 1);
            System.out.println("run thread1");
        });

        Thread thread2 = new Thread(() -> {
            map.put("thread2", 2);
            System.out.println("run thread2");
        });


        thread1.start();
        thread2.start();
        
        // Two threads call the join() method separately, blocking the main thread
        thread1.join();
        thread2.join();

        System.out.println(map.get("thread1") + map.get("thread2"));

        System.out.println("end....");

    }
}

The result of execution is:

As a result, you can see that the sum of the subtrees is 3.At this point, the main thread waits until both subthreads are destroyed before the main thread executes a summary sum, so both threads produce values that already exist.

At the same time, the child thread join() method allows the current thread to wait indefinitely, or it can set the maximum wait time join(long) method, which continues to execute the following code regardless of whether or not the child thread is finished.Use the method to add a timeout parameter, and use the same as the join() method.

CountDownLatch

CountDownLatch allows one or more threads to wait for other threads to complete their operations before continuing with the code behind the current thread.

Use of CountDownLatch: First create the CountDownLatch object and construct the CountDownLatch object by passing in the parameter int.This parameter is the number of execution points the value will wait for.

There are several methods in CountDownLatch:

  • getCount() returns the current number of counters, which is the current remaining number of waits.The official explanation is that this method is often used for debugging and testing purposes.
  • Each time countDown is called, the counter is subtracted by one, but the counter must be greater than 0.
  • The await method blocks the current thread until the counter is 0, and it no longer blocks the current thread.The await(long timeout, TimeUnit unit) method is also provided to set the timeout.

Summarize the cases using CountDownLatch with the following code:

package top.ytao.demo.thread.count;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;

/**
 * https://ytao.top
 *
 * Created by YangTao on 2020/5/17 0017.
 */
public class CountDownLatchTest {

    public static void main(String[] args) throws InterruptedException {

        Map<String, Integer> map = new ConcurrentHashMap<>();

        CountDownLatch count = new CountDownLatch(2);

        Thread thread1 = new Thread(() -> {
            map.put("thread1", 1);
            System.out.println("run thread1");
            count.countDown();
        });

        Thread thread2 = new Thread(() -> {
            map.put("thread2", 2);
            System.out.println("run thread2");
            count.countDown();
        });


        thread1.start();
        thread2.start();

        // Block the current thread until the counter is 0
        count.await();

        System.out.println(map.get("thread1") + map.get("thread2"));

        System.out.println("end.... getCount: " + count.getCount());
    }

}

The results are as follows:

The sum in the figure above is 3 with a counter of 0.

By looking at the CountDownLatch source code, it is mainly implemented through an internal class Sync that inherits the AbstractQueuedSynchronizer class, and its implementation principle is AQS, which is not described here.

CyclicBarrier

CyclicBarrier is a reusable barrier.The implementation principle is to set a barrier in the running of one or more threads, where threads will be blocked until the last thread arrives and the blocked threads will continue executing.

There are two construction methods for CyclicBarrier, CyclicBarrier(int count) and Cyclic Barrier (int count, Runnable barrierAction):

  • A single int parameter construction method that represents the number of threads constructed to reach the barrier.
  • An int and a Runnable parameter construction method, the former representing the number of threads reaching the barrier, and the latter representing the code to be executed after all threads reach the barrier;

Methods in CyclicBarrier:

Method Explain
await() Pre-blocking thread, waitingTrip.signal() orTrip.signalAll() Method wake-up
await(long, TimeUnit) Add two parameters to await(), wait for the timeout time to time out, in unit
breakBarrier() Release barriers, set flags, wake up threads blocked by barriers
isBroken() Is the blocked thread interrupted
reset() Reset CyclicBarrier Object
getNumberWaiting() Number of currently blocked threads
getParties() The total number of threads that reach the barrier, that is, the number specified at creation time

Use CyclicBarrier to implement the above summary:

package top.ytao.demo.thread.count;

import java.util.Map;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CyclicBarrier;

/**
 * https://ytao.top
 *
 * Created by YangTao on 2020/5/17 0017.
 */
public class CyclicBarrierTest {

    public static void main(String[] args) throws BrokenBarrierException, InterruptedException {

        Map<String, Integer> map = new ConcurrentHashMap<>();

        CyclicBarrier barrier = new CyclicBarrier(2, new Thread(()->{
            // Code to execute when all threads reach the barrier
            System.out.println(map.get("thread1") + map.get("thread2"));
            System.out.println("CyclicBarrier end.... ");
        }));

        Thread thread1 = new Thread(() -> {
            map.put("thread1", 1);
            System.out.println("run thread1");
            try {
                barrier.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }

        });

        Thread thread2 = new Thread(() -> {
            map.put("thread2", 2);
            System.out.println("run thread2");
            try {
                barrier.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        });

        thread1.start();
        thread2.start();

    }
}

Execution results:

Executes two sub-threads and calls in themBarrier.await(), the barrier is opened and the last code logic of CyclicBarrier is executed.

From the CyclicBarrier method above, CyclicBarrier is more flexible to use than CountDownLatch, CyclicBarrier's reset() method can reset the counter, and CountDownLatch can only be used once.At the same time, CyclicBarrier has more thread blocking information to provide usage, providing a more flexible way to use it.

summary

All three of the above are provided by the tools in the concurrent package of JDK.In a multithreaded collaboration task, the solution to the counter scenario problem implements that the main thread waits for the worker thread to complete.In practical development applications, the frequency of use is also very high.

Recommended reading

Java Thread Foundation, Start Here

wait/notify mechanism for Java thread communication

JDK Dynamic Proxy and CGLIB Dynamic Proxy that You Must Meet

Dubbo Extension Point Loading Mechanism: From Java SPI to Dubbo SPI

Glue/Unpack Processing in Netty

Topics: Programming Java JDK Dubbo Netty