支持多线程并发的计数器实现

        该计数器支持多个客户端线程并发访问,计数器的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);
		}
	}
}

猜你喜欢

转载自chenjumin.iteye.com/blog/2298094