使用CallableTask / Futures和ObjectMapper时多线程代码的性能问题

时间:2013-04-20 23:56:56

标签: java multithreading jackson future

我正在开发一个基于REST服务的项目,其中我有两个组件,如下所述 -

  1. 将为服务组件提供必要URL's的客户
  2. 然后,Service(REST服务)组件将使用那些URL's从数据库中获取数据。
  3. 一般来说,URL看起来像这样 -

    http://host.qa.ebay.com:8080/deservice/DEService/get/USERID=9012/PROFILE.ACCOUNT,PROFILE.ADVERTISING,PROFILE.DEMOGRAPHIC,PROFILE.FINANCIAL

    上述网址的含义是 - USERID- 9012为我提供数据库中这些列的数据 -

    [PROFILE.ACCOUNT, PROFILE.ADVERTISING, PROFILE.DEMOGRAPHIC, PROFILE.FINANCIAL]

    目前我在客户端组件方面进行基准测试。我发现下面的方法正在time(95 Percentile)周围带来~15ms一堆。

    下面的方法将接受两个参数 -

    List<DEKey> keys- sample data in keys will have USERID=9012

    List<String> reqAttrNames- sample data for reqAttrNames will be-

    [PROFILE.ACCOUNT, PROFILE.ADVERTISING, PROFILE.DEMOGRAPHIC, PROFILE.FINANCIAL]

    以下是代码 -

    public DEResponse getDEAttributes(List<DEKey> keys, List<String> reqAttrNames) {
    
        DEResponse response = null;
        try {
            String url = buildGetUrl(keys,reqAttrNames);
    
            if(url!=null){
                List<CallableTask<DEResponse>> tasks = new ArrayList<CallableTask<DEResponse>>();
                CallableTask<DEResponse> task = new DEResponseTask(url); 
                tasks.add(task);
    
                // STEP 2: Execute worker threads for all the generated urls
                List<LoggingFuture<DEResponse>> futures = null;
                try {
                    long waitTimeout = getWaitTimeout(keys);
                    futures = executor.executeAll(tasks, null, waitTimeout, TimeUnit.MILLISECONDS);
    
                    // STEP 3: Consolidate results of the executed worker threads
                    if(futures!=null && futures.size()>0){
                        LoggingFuture<DEResponse> future = futures.get(0);
                        response = future.get();
                    }
                } catch (InterruptedException e1) {
                    logger.log(LogLevel.ERROR,"Transport:getDEAttributes Request timed-out :",e1);
                }
            }else{
                //
            }
        }  catch(Throwable th) {
    
        }
    
        return response;
    }
    

    上面的方法会让我回到DEResponse对象。

    以下是DEResponseTask class

    public class DEResponseTask  extends BaseNamedTask implements CallableTask<DEResponse> {
    
            private final ObjectMapper m_mapper = new ObjectMapper();
    
            @Override
            public DEResponse call() throws Exception {
                URL url = null;
                DEResponse DEResponse = null;
                try {
                    if(buildUrl!=null){
                        url = new URL(buildUrl);
    
                        DEResponse = m_mapper.readValue(url, DEResponse.class);
    
                    }else{
                        logger.log(LogLevel.ERROR, "DEResponseTask:call is null ");
                    }
                } catch (MalformedURLException e) {
    
                }catch (Throwable th) {
    
                }finally{
                }
    
                return DEResponse;
            }
        }
    

    这个多线程代码的编写方式有问题吗?如果是的话,我怎样才能提高效率呢?

    executeAll executor方法的签名,因为在我的公司,他们有自己的执行者,将执行Sun Executor类 -

    /**
         * Executes the given tasks, returning a list of futures holding their 
         * status and results when all complete or the timeout expires, whichever
         * happens first.  <tt>Future.isDone()</tt> is <tt>true</tt> for each
         * element of the returned list.  Upon return, tasks that have not completed
         * are cancelled.  Note that a <i>completed</i> task could have terminated
         * either normally or by throwing an exception.  The results of this method
         * are undefined if the given collection is modified while this operation is
         * in progress.  This is entirely analogous to
         * <tt>ExecutorService.invokeAll()</tt> except for a couple of important
         * differences.  First, it cancels but does not <b>interrupt</b> any 
         * unfinished tasks, unlike <tt>ExecutorService.invokeAll()</tt> which
         * cancels and interrupts unfinished tasks.  This results in a better 
         * adherence to the specified timeout value, as interrupting threads may
         * have unexpected delays depending on the nature of the tasks.  Also, all 
         * eBay-specific features apply when the tasks are submitted with this 
         * method.
         * 
         * @param tasks the collection of tasks
         * @param timeout the maximum time to wait
         * @param unit the time unit of the timeout argument
         * @return a list of futures representing the tasks, in the same sequential
         * order as produced by the iterator for the given task list.  If the 
         * operation did not time out, each task will have completed.  If it did
         * time out, some of these tasks will not have completed.
         * @throws InterruptedException if interrupted while waiting, in which case
         * unfinished tasks are cancelled
         */
        public <V> List<LoggingFuture<V>> executeAll(Collection<? extends CallableTask<V>> tasks, 
                                                     Options options, 
                                                     long timeout, TimeUnit unit)
                throws InterruptedException {
            return executeAll(tasks, options, timeout, unit, false);
        }
    

    更新: -

    这个组件一旦我增加程序的负载就会花费时间,通过将线程增加到20进行基准测试

    newFixedThreadPool(20)

    但我相信如果我使用 -

    ,这个组件可以正常工作

    newSingleThreadExecutor

    我能想到的唯一原因可能是上面的代码,有一个阻塞调用,这就是线程被阻塞的原因,这就是为什么需要时间?

    更新: -

    所以这一行应该这样写? -

    if(futures!=null && futures.size()>0){
                        LoggingFuture<DEResponse> future = futures.get(0);
                        //response = future.get();//replace this with below code-
    
                        while(!future.isDone()) {
                            Thread.sleep(500);
                        } 
    
                        response = future.get();
                    }
    

2 个答案:

答案 0 :(得分:0)

除了使用复杂的非标准Executor之外,我没有看到任何导致性能损失的内容。我意识到你在使用Executor的问题上没有任何选择,但出于好奇,我会尝试用ThreadPoolExecutor替换它,看看这是否有所不同,并提出来如果你注意到一个重大改进,你工作的权力 - 在我的工作中我们发现由另一个部门编写的加密库是绝对的废话(我们的CPU时间的80-90%用于他们的代码)并成功游说他们重写它。

编辑:

public class Aggregator implements Runnable {
    private static ConcurrentLinkedQueue<Future<DEResponse>> queue = new ConcurrentLinkedQueue<>();
    private static ArrayList<DEResponse> aggregation = new ArrayList<>();

    public static void offer(Future<DEResponse> future) {
        queue.offer(future);
    }

    public static ArrayList<DEResponse> getAggregation() {
        return aggregation;
    }

    public void run() {
        while(!queue.isEmpty()) { // make sure that all of the futures are added before this loop starts; better still, if you know how many worker threads there are then keep a count of how many futures are in your aggregator and quit this loop when aggregator.size() == [expected number of futures]
            aggregation.add(queue.poll().get());
        }
    }
}

public void getDEAttributes(List<DEKey> keys, List<String> reqAttrNames) {
    try {
        if(url!=null){
            try {
                futures = executor.executeAll(tasks, null, waitTimeout, TimeUnit.MILLISECONDS);
                if(futures!=null && futures.size()>0){
                    Aggregator.offer(futures.get(0));
                }
            }
        }
    }
}

答案 1 :(得分:0)

如果我正确阅读了您的代码,则存在一个明显的性能问题。这样:

public class DEResponseTask  extends BaseNamedTask implements CallableTask<DEResponse> {
    private final ObjectMapper m_mapper = new ObjectMapper();

每个任务调用一次,ObjectMapper实例的创建非常昂贵。

有很多方法可以解决这个问题,但您可能想要:

  1. 使m_mapper引用为静态(仅创建一次) - 映射器配置后可以安全共享,或者
  2. 传入共享ObjectMapper(分享是安全的)
  3. 这样做会对JSON处理效率产生重大影响。