欢迎访问昆山宝鼎软件有限公司网站! 设为首页 | 网站地图 | XML | RSS订阅 | 宝鼎邮箱 | 后台管理


新闻资讯

MENU

软件开发知识

except that the action is perf 劳务派遣信息管理系统 ormed atomically.这

点击: 次  来源:宝鼎软件 时间:2017-07-31

原文出处: 常大皮卡丘

最近在做接口限流时涉及到了一个有意思问题,牵扯出了关于concurrentHashMap的一些用法,劳务派遣管理系统,以及CAS的一些观念。限流算法许多,我主要就以最简朴的计数器法来做引。先抽象化一下需求:统计每个接口会见的次数。一个接口对应一个url,也就是一个字符串,每挪用一次对其举办加一处理惩罚。大概呈现的问题主要有三个:

  1. 多线程会见,需要选择符合的并发容器
  2. 漫衍式下多个实例统计接口流量需要共享内存
  3. 流量统计应该尽大概不损耗处事器机能

但这次的博客并不是想描写怎么去实现接口限流,而是主要想描写一下碰着的问题,所以,第二点临时不思量,即不利用Redis。

说到并发的字符串统计,当即让人遐想到的数据布局即是ConcurrentHashpMap<String,Long> urlCounter;
假如你方才打仗并发大概会写出如代码清单1的代码

代码清单1:

public class CounterDemo1 {

    private final Map<String, Long> urlCounter = new ConcurrentHashMap<>();

    //接口挪用次数+1
    public long increase(String url) {
        Long oldValue = urlCounter.get(url);
        Long newValue = (oldValue == null) ? 1L : oldValue + 1;
        urlCounter.put(url, newValue);
        return newValue;
    }

    //获取挪用次数
    public Long getCount(String url){
        return urlCounter.get(url);
    }

    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(10);
        final CounterDemo1 counterDemo = new CounterDemo1();
        int callTime = 100000;
        final String url = "http://localhost:8080/hello";
        CountDownLatch countDownLatch = new CountDownLatch(callTime);
        //模仿并发环境下的接口挪用统计
        for(int i=0;i<callTime;i++){
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    counterDemo.increase(url);
                    countDownLatch.countDown();
                }
            });
        }
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        executor.shutdown();
        //期待所有线程统计完成后输出挪用次数
        System.out.println("挪用次数:"+counterDemo.getCount(url));
    }
}

console output:
挪用次数:96526

都说concurrentHashMap是个线程安详的并发容器,所以没有显示加同步,实际结果呢并不如所愿。

问题就出在increase要领,劳务派遣管理系统,concurrentHashMap能担保的是每一个操纵(put,get,delete…)自己是线程安详的,可是我们的increase要领,对concurrentHashMap的操纵是一个组合,先get再put,所以多个线程的操纵呈现了包围。假如对整个increase要领加锁,那么又违背了我们利用并发容器的初志,因为锁的开销很大。我们有没有要领改进统计要领呢?
代码清单2摆列了concurrentHashMap父接口concurrentMap的一个很是有用可是又经常被忽略的要领。

代码清单2:

    /**
     * Replaces the entry for a key only if currently mapped to a given value.
     * This is equivalent to
     *  <pre> {@code
     * if (map.containsKey(key) && Objects.equals(map.get(key), oldValue)) {
     *   map.put(key, newValue);
     *   return true;
     * } else
     *   return false;
     * }</pre>
     *
     * except that the action is performed atomically.
     */
    boolean replace(K key, V oldValue, V newValue);

这其实就是一个最典范的CAS操纵,except that the action is performed atomically.这句话真是帮了大忙,我们可以担保较量和配置是一个原子操纵,当A线程实验在increase时,旧值被修改的话就回导致replace失效,而我们只需要用一个轮回,不绝获取最新值,直到乐成replace一次,软件开发,即可完成统计。

改造后的increase要领如下

代码清单3:

public long increase2(String url) {
        Long oldValue, newValue;
        while (true) {
            oldValue = urlCounter.get(url);
            if (oldValue == null) {
                newValue = 1l;
                //初始化乐成,退出轮回
                if (urlCounter.putIfAbsent(url, 1l) == null)
                    break;
                //假如初始化失败,说明其他线程已经初始化过了
            } else {
                newValue = oldValue + 1;
                //+1乐成,退出轮回
                if (urlCounter.replace(url, oldValue, newValue))
                    break;
                //假如+1失败,说明其他线程已经修悔改了旧值
            }
        }
        return newValue;
    }

console output:
挪用次数:100000