This article has participated in the “Digitalstar Project” and won a creative gift package to challenge the creative incentive money.

The introduction

Program ape small maple recently received TL assignment of new tasks, maintenance of a new application before, in the development of new requirements at the same time, can not help but also need to check some previous code buried in the pit. Recently, there has been a problem with high CPU in the online environment service. Let’s take a look at how the programmer Xiao Feng analyzed and solved the problem.

The optimization process

background

Note: because it is the company’s online business, the business description and code here have been desensitized.

There was a problem that the service CPU occupied too much on line, so Xiao Feng used the top command to locate the process ID with a high CPU, and then combined with the jstack command to export the thread information of the process with a high CPU, and locate the problem code (how to troubleshoot online problems is not the focus of this paper, and it has been briefly mentioned here. Then write a special article to focus on).

To start with the business context, the code in question takes the information from MQ and caches it in a queue, and then processes the business after retrieving the data from the queue through a separate thread. Xiao Feng found that in this code, a while loop was used to continuously obtain data from the queue to judge whether the map taken out was empty for subsequent business processing. If it was empty, data would continue to be obtained. On the surface there seems to be no problem. If there is no data in the queue, the CPU is idle because the program is executing continuously in the while loop. So how to solve the problem?

CPU utilization when the while loop is not running during local tests:

CPU utilization after running the while loop during local test:

Optimization idea

The problem with this code is that it continues to fetch and execute judgments when there is no data in the queue, wasting CPU resources on the computer. LinkedBlockingQueue queue queue queue queue queue queue queue queue queue queue queue queue queue queue queue queue queue queue queue queue queue queue queue queue queue queue queue queue queue queue queue Can we use a block-wake approach to solve the while idling problem using the idea of the take method? At the thought of here, little Maple some excited, as if to see the dawn, immediately rubbed his hands, ready to start coding test.

To optimize the implementation

The original while loop code looks like this:


public static class TakeDataThread extends Thread {

    @Override
    public void run(a) {
        // loop to get data
        while(true) {
            Map<String, String> map = QueueData.getRecordList(1.2L);
            // If map is always empty, the CPU will be idle
            if (CollectionUtils.isEmpty(map)) {
                System.out.println("continue");
                continue;
            }
            System.out.println("next step"); }}}public static class QueueData {

private static volatile LinkedBlockingQueue<Map> recordInfoQueue;
    public static Map<String, String> getRecordList(int size, Long timeout) {
        if(Objects.isNull(recordInfoQueue)) {
            return Collections.emptyMap();
        }
        returnrecordInfoQueue.poll(); }}Copy the code

To optimize the implementation

1. Add blocking to getRecordList to block when the queue is empty and the map obtained is empty.


public static class QueueData {
    
    private static volatile LinkedBlockingQueue<Map> recordInfoQueue;

    private final static ReentrantLock handleLock = new ReentrantLock();

    private final staticCondition notEmpty = handleLock.newCondition(); .public static Map<String, String> getRecordList(int size, Long timeout) {
        Map<String, String> map = null;
        try {
            handleLock.lockInterruptibly();
            // The queue is empty and blocks
            while (recordInfoQueue == null|| CollectionUtils.isEmpty(recordInfoQueue.poll())) { notEmpty.await(); }}catch (InterruptedException e) {
            e.printStackTrace();
        }
        finally {
            handleLock.unlock();
        }
        returnmap; }... }Copy the code

Wake up during queue initialization and data caching in network queues.

public static class QueueData {...public static void putRecord(Map alarmVo) throws InterruptedException {
        if (recordInfoQueue == null) {
            synchronized (QueueData.class) {
                if(recordInfoQueue == null){
                    recordInfoQueue = new LinkedBlockingQueue(10000);
                }
            }
        }
        recordInfoQueue.put(alarmVo);
        // Wake up the thread when the queue is created and data is cached
        signalNotEmpty();
}


private static void signalNotEmpty(a) {
    final ReentrantLock takeLock = QueueData.handleLock;
    takeLock.lock();
    try {
        notEmpty.signal();
    } finally{ takeLock.unlock(); }}... }Copy the code

Debugging code

Debug code locally:


public class TestMain {


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

    System.out.println("--------takeDataThread start------");
    TakeDataThread takeDataThread = new TakeDataThread();
    takeDataThread.start();
    System.out.println("--------takeDataThread end------");

    System.out.println("--------dataNotifyThread start------");
    DataNotifyThread  dataNotifyThread = new DataNotifyThread(0);
    dataNotifyThread.start();
    System.out.println("--------dataNotifyThread end------");

    System.out.println("--------dataNotifyThread2 start------");
    DataNotifyThread  dataNotifyThread2 = new DataNotifyThread(1);
    dataNotifyThread2.start();
    System.out.println("--------dataNotifyThread2 end------"); }... }Copy the code

The TakeDataThread startup is performed in the main thread

Switch to the TakeDataThread

Because the queue is not initialized to NULL, the thread blocks here.

The TakeDataThread thread status changed from RUNNING to WAIT

Switch to the main thread and continue with the following code

Start the DataNotifyThread thread in the main thread

Switch to the DataNotifyThread thread. After the queue is initialized, the previously blocked TakeDataThread is awakened and the thread status changes from WAIT to RUNNING

At this point, Xiao Maple has completed the while loop to block wake mode, greatly reducing the CPU usage of the service when making loop judgments.

conclusion

After the above code optimization process, the program ape small Maple finally solved the problem of processing data thread CPU is too high, small Maple will be similar to the problems of the loop in the service are modified, after the test service corresponding TO the CPU usage has a significant decline, small Maple relief, finally can go off work, I thought I’d add a drumstick to my brain cells when I got home. The story of Little Maple will continue, and what technical challenges he will encounter, please stay tuned.