All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.phantomthief.util.TickerBatchInvoker Maven / Gradle / Ivy

There is a newer version: 0.2.21
Show newest version
package com.github.phantomthief.util;

import static com.github.phantomthief.tuple.Tuple.tuple;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

import com.github.phantomthief.collection.BufferTrigger;
import com.github.phantomthief.collection.impl.SimpleBufferTrigger;
import com.github.phantomthief.tuple.TwoTuple;

/**
 * @author w.vela
 * Created on 16/5/21.
 */
public class TickerBatchInvoker implements Function> {

    private final ThrowableFunction, Map, ? extends Throwable> batchInvoker;
    private final Executor executor;
    private final BufferTrigger>> bufferTrigger;

    private TickerBatchInvoker(long ticker,
            ThrowableFunction, Map, ? extends Throwable> batchInvoker,
            Executor executor) {
        this.batchInvoker = batchInvoker;
        this.executor = executor;
        this.bufferTrigger = SimpleBufferTrigger.newBuilder() //
                .setContainer(ConcurrentHashMap::new, this::enqueue) //
                .on(ticker, MILLISECONDS, 1) //
                .consumer(this::batchInvoke) //
                .build();
    }

    public static Builder newBuilder() {
        return new Builder();
    }

    private boolean enqueue(Map>> map,
            TwoTuple> e) {
        map.compute(e.getFirst(), (k, list) -> {
            if (list == null) {
                list = new ArrayList<>();
            }
            synchronized (list) {
                list.add(e.getSecond());
            }
            return list;
        });
        return true;
    }

    private void batchInvoke(Map>> map) {
        executor.execute(() -> {
            try {
                Map result = batchInvoker.apply(map.keySet());
                map.forEach((key, futures) -> {
                    V v = result.get(key);
                    // 虽然框架会尽力保证enqueue/consume是互斥的,但是这里还是重复保证下
                    synchronized (futures) {
                        for (CompletableFuture future : futures) {
                            future.complete(v);
                        }
                    }
                });
            } catch (Throwable e) {
                for (List> futures : map.values()) {
                    synchronized (futures) {
                        futures.stream() //
                                .filter(future -> !future.isDone()) //
                                .forEach(future -> future.completeExceptionally(e));
                    }
                }
            }
        });
    }

    @Override
    public CompletableFuture apply(K key) {
        CompletableFuture future = new CompletableFuture<>();
        bufferTrigger.enqueue(tuple(key, future));
        return future;
    }

    public static class Builder {

        private long ticker;
        private Executor executor;

        private Builder() {
        }

        public Builder ticker(long time, TimeUnit unit) {
            this.ticker = unit.toMillis(time);
            return this;
        }

        public Builder executor(Executor executor) {
            this.executor = executor;
            return this;
        }

        public Builder threads(int nThreads) {
            this.executor = Executors.newFixedThreadPool(nThreads);
            return this;
        }

        public  TickerBatchInvoker build(
                ThrowableFunction, Map, ? extends Throwable> batchInvoker) {
            checkNotNull(batchInvoker);
            ensure();
            return new TickerBatchInvoker<>(ticker, batchInvoker, executor);
        }

        private void ensure() {
            if (ticker <= 0) {
                ticker = SECONDS.toMillis(1);
            }
            if (executor == null) {
                executor = Executors.newCachedThreadPool();
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy