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