想起很久以前写的一个工具,记录下吧。
原因: 当时要做一个优化, 大量的数据拉取后,需要同步到 ES,之前是多个线程一条条的插入,
这里就典型的生产消费问题, 生产一条,丢到线程池消费一条,然而当时的生产者性能远的高于消费者性能,于是决定做这样一个队列,生产者一条一条的产生数据后进入队列,消费者等待队列装满或者1秒以后还没装满就去消费。
这样改造可以一次批量插入几十条数据到 ES,实际性能提升了很多(具体提升多少忘了)
大概实现:“读” “写”相互等待,“写”满时或者达到超时时间唤醒 ”读“,“读”完立即唤醒 “写”
public class TestSome {
@Slf4j
public static class TakeAllQueue<T> {
private final ReentrantLock lock = new ReentrantLock();
private int max;
private List<T> list;
private Condition addCondition = lock.newCondition();
private Condition readCondition = lock.newCondition();
// 加了锁,所以用普通的int也能计数,这里用的AtomicInteger,
//当前缓存中的数量
private volatile AtomicInteger counting = new AtomicInteger(0);
//已添加数量
private volatile AtomicInteger totalCounting = new AtomicInteger(0);
//已读取数量
private volatile AtomicInteger finishedCounting = new AtomicInteger(0);
/**
* @param max
* @return
* @Author yq
* @Description 指定最大max个线程完成任务后就开始读取数据
**/
public TakeAllQueue(int max) {
this.max = max;
this.list = new ArrayList<>(max);
}
/**
* 添加数据,阻塞方式
* @param obj
* @return void
* @Author yq
* @Date 17:41 2020-12-9
**/
public void add(T obj) {
try {
lock.lockInterruptibly();
while (counting.get() >= max) {
addCondition.await();
}
counting.addAndGet(1);
totalCounting.addAndGet(1);
list.add(obj);
readCondition.signal();
} catch (Exception e) {
log.error(e.getMessage(), e);
} finally {
lock.unlock();
}
}
private List<T> readAll() {
List<T> result = new ArrayList<>(list);
list.clear();
counting.set(0);
addCondition.signal();
finishedCounting.addAndGet(result.size());
return result;
}
/**
* 超过一定时间直接获取,超时时间内,list装满,直接返回, 单位:秒!!!
* @param secondsTimeout 秒数
* @return java.util.List<T>
* @Author yq
* @Description
**/
public List<T> getAllBlocking(long secondsTimeout) {
try {
lock.lock();
long expire = System.currentTimeMillis() + secondsTimeout * 1000;
while (counting.get() < max) {
if (expire <= System.currentTimeMillis()) {
break;
}
addCondition.signal();
readCondition.await(secondsTimeout, TimeUnit.SECONDS);
}
List<T> result = readAll();
addCondition.signal();
return result;
} catch (Exception e) {
log.error(e.getMessage(), e);
return Collections.emptyList();
} finally {
lock.unlock();
}
}
public boolean isFinished() {
return finishedCounting.get() >= totalCounting.get();
}
public int getCount() {
return totalCounting.get();
}
}
public static void main(String[] args) {
TakeAllQueue<Integer> queue = new TakeAllQueue<>(20);
//生产者
ExecutorService producer = Executors.newFixedThreadPool(2);
//消费者
ExecutorService consumer = Executors.newFixedThreadPool(10);
//生产奇数
producer.execute(() -> {
for (int i = 1; i < 10000; i += 2) {
queue.add(i);
}
});
//生产偶数
producer.execute(() -> {
for (int i = 0; i < 10000; i += 2) {
queue.add(i);
}
});
//20个消费者同事消费
for (int i = 0; i < 20; i++) {
consumer.execute(() -> {
while (queue.getCount() < 10000) {
try {
//模拟慢消费,
ThreadLocalRandom random = ThreadLocalRandom.current();
long sleepTime = random.nextLong(100);
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
List<Integer> result = queue.getAllBlocking(1);
System.out.println(Thread.currentThread().getName() + " 消费数量 " + queue.getCount() + " " + result + " 总数量 " + result.size());
}
});
}
}
}
运行结果如下:确实是每次拉取20条数据