该计数器支持多个客户端线程并发访问,计数器的key/value存储在一个静态型的AtomicLongMap对象中,另外有一个守护线程定期将计数器的数据取出,然后存储到数据库、文件等地方。该范例可以做适当的调整,然后应用到统计页面访问量、菜单点击量、IP访问量等计数的场景。下面是计数器的源码:
public class PageViewsStatistics { private static PageViewsStatistics pvStat = null; private static ScheduledExecutorService service = null; private static AtomicLongMap<String> pvCounterMap = AtomicLongMap.create(); //线程安全,支持并发 private static ReentrantLock lock = new ReentrantLock(); //锁 private static int MONITOR_INITIAL_DELAY_SECONDS = 3; //监控初始延迟秒数 private static int MONITOR_INTERVAL_SECONDS = 10; //监控间隔秒数 private static Map<String, Long> map2 = new HashMap<String, Long>(); /** * 计数值增加1 * @param key */ public long incr(String key){ long result = -1; while(true){ if(!lock.isLocked()){ //在做pop动作时,不能进行计数,等待直到完成pop动作 result = pvCounterMap.incrementAndGet(key); break; }else{ try { TimeUnit.MILLISECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } return result; } /** * 获取单实例对象 * @return */ public static PageViewsStatistics newInstance(){ if(pvStat == null){ synchronized (PageViewsStatistics.class) { if(pvStat == null){ pvStat = new PageViewsStatistics(); } } } return pvStat; } /** * 构造函数 */ public PageViewsStatistics(){ createExecutorService(); startMonitor(); } /** * 创建计划任务线程池 */ private void createExecutorService() { if(service == null){ service = Executors.newScheduledThreadPool(1, new ThreadFactory() { @Override public Thread newThread(Runnable r) { Thread thread = new Thread(r); thread.setDaemon(true); //宿主线程 return thread; } }); } } /** * 开始监控计数器 */ private void startMonitor(){ //表示在上一个任务结束执行之后,延迟多少秒之后再执行,是从上一个任务结束时开始计算
service.scheduleWithFixedDelay( new StatMonitorRunner(), MONITOR_INITIAL_DELAY_SECONDS, MONITOR_INTERVAL_SECONDS, TimeUnit.SECONDS); } /** * 获取访问量计数器中的访问量累计值,放到一个临时Map中,然后清空访问量计数器 * @return */ private Map<String, Long> popCounter(){ Map<String, Long> newMap = new HashMap<String, Long>(); lock.lock(); try{ for(Iterator<String> it = pvCounterMap.asMap().keySet().iterator(); it.hasNext(); ){ String key = it.next(); newMap.put(key, pvCounterMap.get(key)); } pvCounterMap.clear(); }finally{ lock.unlock(); } return newMap; } class StatMonitorRunner implements Runnable{ @Override public void run() { Map<String, Long> map = popCounter(); //可以将计数值写到数据库中 for(Iterator<String> it = map.keySet().iterator(); it.hasNext(); ){ String key = it.next(); if(map2.containsKey(key)){ map2.put(key, new Long(map2.get(key).longValue() + map.get(key).longValue())); }else{ map2.put(key, map.get(key)); } } System.out.println(map2); } } }
下面是测试代码:
public class AtomicLongMapTest { public static void main(String[] args) { ScheduledExecutorService service = Executors.newScheduledThreadPool(4); for(int i=0; i<4; i++){ AtomicLongMapTest t = new AtomicLongMapTest(); service.scheduleWithFixedDelay(t.new MyRunner("name"), 0, 1000, TimeUnit.MILLISECONDS); } } class MyRunner implements Runnable{ private String name; public MyRunner(String name){ this.name = name; } @Override public void run() { long l = PageViewsStatistics.newInstance().incr(this.name); System.out.println(this.name + " > " + l); } } }