Using and improving EvictingQueue in guava

Keywords: Programming Java network Google Apache

I. Introduction

Because some servers in the service are abroad, the network is very unstable, and the http request is very volatile. So we hope to switch to other servers to send http requests through http proxy when the network slows down.

What if the definition slows down?

If the last N http requests are executed and the execution time exceeds the threshold T more than or equal to M, the current network is considered slow.

Therefore, we need to save the time of the last N http requests. First, we need to determine a FIFO queue with a fixed size. In order to avoid repeated wheel building, we first decide to use the EvictingQueue of guava.

2, Basic use of EvictingQueue

@Test
public void testEvictingQueue(){
    EvictingQueue<Integer> queue = EvictingQueue.create(5);
    for (int i = 0; i < 10; i++) {
        queue.add(i);
        System.out.println(String.format("Current queue size:%d,Elements in queue:%s",queue.size(),StringUtils.join(queue.iterator(), ',')));
    }
}

EvictingQueue is created through create. The parameter is the maximum number of elements stored in the queue. After this number is exceeded, the first element added will be removed.

3, Problems with EvictingQueue

The problem with EvictingQueue is that it is non thread safe. As you can see from the source code of EvictingQueue, it is actually the encapsulated ArrayDeque.

The following code can easily reproduce the concurrent access problem of EvictingQueue.

import com.google.common.collect.EvictingQueue;
import org.apache.commons.lang3.StringUtils;
import org.junit.Test;

import java.util.Iterator;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class EvictingQueueTest {

    @Test
    public void testEvictingQueue(){
        EvictingQueue<Integer> queue = EvictingQueue.create(5);
        for (int i = 0; i < 10; i++) {
            queue.add(i);
            System.out.println(String.format("Current queue size:%d,Elements in queue:%s",queue.size(),StringUtils.join(queue.iterator(), ',')));
        }
    }

    public static void main(String[] arg){
        EvictingQueue<Integer> queue = EvictingQueue.create(10);
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        //Ten production threads continuously write data to the queue
        for(int i=0;i<10;i++){
            executorService.submit(new Producer(queue));
        }
        //A production thread continuously detects the number of satisfied conditions in the queue
        new Thread(new Consumer(queue)).start();
    }

    private static class Producer implements Runnable{

        private EvictingQueue<Integer> queue;

        public Producer(EvictingQueue<Integer> queue) {
            this.queue = queue;
        }

        @Override
        public void run() {
            Random random = new Random();
            while (true){
                queue.add(random.nextInt(100));
            }
        }
    }

    private static class Consumer implements Runnable{

        private EvictingQueue<Integer> queue;

        public Consumer(EvictingQueue<Integer> queue) {
            this.queue = queue;
        }

        @Override
        public void run() {
            while (true){
                int count = 0;
                Iterator<Integer> iterator = queue.iterator();
                while (iterator.hasNext()){
                    Integer integer = iterator.next();
                    if(integer < 50){
                        count++;
                    }
                }
                System.out.println("count:" + count);
            }
        }
    }
}

4, Improvement scheme of EvictingQueue

In order to deal with the concurrent access problem of EvictingQueue, we wrote a class to solve this problem

Because it is mainly for statistical monitoring and does not require absolute accuracy of data, synchronization and other synchronizations are not used and can be added by yourself.

import java.io.Serializable;
import java.util.function.Predicate;

public class EvictingArray<T> implements Serializable {

    private static final long serialVersionUID = 0L;

    private Object[] elements;

    private int index;

    private int capacity;

    private int size;

    public static EvictingArray create(int capacity){
        return new EvictingArray(capacity);
    }

    public EvictingArray(int capacity) {
        this.elements = new Object[capacity];
        this.capacity = capacity;
        this.size = 0;
        this.index = 0;
    }

    public void add(T element){
        elements[index++ % capacity] = element;
        if(size < capacity){
            size++;
        }
    }

    public void clear(){
//        Arrays.fill(elements,null);
        this.size = 0;
        this.index = 0;
    }

    //Get the number of elements that meet the conditions
    public int getQualifiedNums(Predicate<T> predicate){
        int num = 0;
        for(Object ele : elements){
            if(predicate.test((T) ele)){
                num++;
            }
        }
        return num;
    }

    public int getSize() {
        return size;
    }
}

Test EvictingArray:


import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Predicate;

public class EvictingArrayTest {

    public static void main(String[] arg){
        EvictingArray<Integer> queue = EvictingArray.create(10);
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        for(int i=0;i<10;i++){
            executorService.submit(new Producer(queue));
        }
        new Thread(new Consumer(queue)).start();
    }

    private static class Producer implements Runnable{

        private EvictingArray<Integer> array;

        public Producer(EvictingArray<Integer> array) {
            this.array = array;
        }

        @Override
        public void run() {
            Random random = new Random();
            while (true){
                array.add(random.nextInt(100));
            }
        }
    }

    private static class Consumer implements Runnable{

        private EvictingArray<Integer> array;

        public Consumer(EvictingArray<Integer> array) {
            this.array = array;
        }

        @Override
        public void run() {
            Predicate predicate = new Predicate<Integer>(){
                @Override
                public boolean test(Integer integer) {
                    return integer < 50;
                }
            };
            while (true){
                System.out.println("count:" + array.getQualifiedNums(predicate));
            }
        }
    }
}

Posted by rlalande on Fri, 10 Apr 2020 05:00:36 -0700