多线程分段读取数据库

有这样一个需求,定时任务产生多个线程读取一张表,然后每个线程扫描这个表的一部分,将记录调用预先定义好的Web Service接口。


该问题可以分解为几个部分,第一、定时任务下的线程管理;第二、表的分段读取;第三、资源保护


第一部分,由于LZ的项目采用Spring架构,于是采用基于注解的ThreadPoolTaskExecutor,applicationContext.xml中的配置如下:


		
		
		
		
	

    @Scheduled(cron = "0 30 0 * * ?")
    public void startWork() {   
        xxService.service();
    }


Service自然采用基于Spring的注解,可以完美的解决Transactional问题,代码如下:

public void service() {
        
        logger.info("启动了:" + threadPoolTaskExecutor.getCorePoolSize() + "个线程");
        for (int i = 0;i < threadPoolTaskExecutor.getCorePoolSize();i++){
            threadPoolTaskExecutor.execute(new XXThread(paramlist));
        }
    }

第二个问题,表的分段读取,这就落在我们的XXThread类里面了,顾名思义,这是个实现了Runnable接口的线程类:

class XXThread implements Runnable {
    public XXThread(paramlist) {
        ......
    }
    @Override
    public void run() {
        dosth();
    }
     
    /**
     * 根据线程的索引号,分段取服务队的数据,如果一次没取完,则循环取
     * @param extOffset 额外的偏移量 初始为0
     * @param index 线程的索引号
     * @return
     */
    private Map getSthByIndex(int extOffset,int index) {
        Map map = new HashMap();
        List list = new ArrayList();
        
        long leftSize = aa.selectCount() - extOffset;
      
        if (leftSize <= 0) {
            map.put("list", list);
            map.put("loop", -1);
            return map;
        }
        
        double x = (double)(leftSize)/(double)count;
        
        long limit = (long) Math.ceil(x); //不可能为0或负数
      
        
        int loop = 0;
        //如果超过了限制
        if (limit > MAXRECORDS) {
            limit = MAXRECORDS;
            loop = (int) Math.ceil((double)leftSize/(double)(MAXRECORDS * count));//需要循环的次数
            System.out.println("loop:" + loop);
        }
        
        long offset = extOffset + index * limit;//对于每个线程而已,其实际的偏移量的起始地址
        list = aa.getListInLimit(offset, limit);
        
        map.put("list", list);
        map.put("loop", loop);
        return map;
    }
    
    //每次最大记录数为500条
    private final static int MAXRECORDS = 500;
    //线程的索引号
    private int index;
    //线程的数量
    private int count;
}

注意这个方法getSthByIndex,它实现了分段扫描的关键逻辑,但要想完整的使用该功能,还需要一个一些配合,这个配合的方法则是我们XXThread的主要逻辑方法:

public synchronized void doSth() {
        //线程号、线程扫描表的次数组成的map
        Map idxMap = new HashMap();
        
        int extOffset = 0;//额外的偏移量,线程进来的次数 * 线程数 * 每次最多取的数据总数
        
        int loop = 0;//需要扫描的次数
        
        do {
            
            Map map = getSthByIndex(extOffset,this.index);
            
            loop = (int)map.get("loop");
            
            @SuppressWarnings("unchecked")
            List lists = (List)map.get("list");
            Iterator iterator = lists.iterator();
            
            while (iterator.hasNext()) {
                
                干活 
                
            }
            
            if (!idxMap.containsKey(index)) {
                idxMap.put(index, 1);
            } else {
                int num = idxMap.get(index);
                idxMap.put(index, (num + 1));//该线程进来了多少次
            }
            
            extOffset = idxMap.get(index) * count * MAXRECORDS;
            
        } while (loop >= 0);
        
    }


注意这个idxMap,这个是关键代码之一。

至于资源保护问题,涉及到自己的业务逻辑,就不在这里贴出来了。


至于怎样从数据库里分段读取数据的问题,不明白的就去看看limit的用法吧。


自此,比较满意的解决了这个需求。

你可能感兴趣的:(J2EE)