阻塞队列(同步):
LinkedBlockingQueue<E>:java.util.concurrent这是一个基于链表实现的的阻塞队列(当多个线程操作共同的队列时不需要额外的同步),他的范围可以自由的设定,如果不设定则默认为Integer.MAX_VALUE.
ArrayBlockingQueue也是一个阻塞队列,底层有数组实现,但他必须指定队列的大小.
非阻塞队列(异步):
ConcurrentLinkedQueue<E>:java.util.concurrent这也是一个基于链表的线程安全的非阻塞队列,他是一个无界队列,当许多的线程共享访问一个公共的collection时,ConcurrentLinkedQueue队列是一个很好地选择,此队列不允许null元素.
在我们现实生活中的每次排队就是一个队列,在java编程中也有好多问题需要用队列解决:网络爬虫抓取了大量网络信息生成的warc文件需要我们来解析,如何解析这些文件,又如何更新这些文件在数据库中的信息呢?
在数据库中设置一个字段status来标注warc文件是否解析过,0为未解析,1为已解析。程序中通过Timer调度不断地查询数据库,并将未解析的warc文件信息放入到队列当中,遍历队列中信息,解析队列中的文件并将解析后的数据更新数据库.
1、建立队列.
import java.util.*; import java.util.concurrent.LinkedBlockingQueue; public class TaskQueue { private static LinkedBlockingQueue<SolrTask> queue = new LinkedBlockingQueue<SolrTask>(2000); public static boolean enQueue(SolrTask task){ boolean status = queue.offer(task); System.out.println("queue Size = "+TaskQueue.size()); return status; } public static boolean enQueue(Collection<SolrTask> tasks){ boolean status = queue.addAll(tasks); System.out.println("Queue Size = "+TaskQueue.size()); return status; } public static SolrTask deQueue(){ SolrTask task = queue.poll(); System.out.println("dequene Queue Size = "+TaskQueue.size()); return task; } public static boolean contains(SolrTask task){ return queue.contains(task); } public static List<SolrTask> getAll(){ List<SolrTask> taskList = new ArrayList<SolrTask>(); Iterator<SolrTask> it = queue.iterator(); while(it.hasNext()){ SolrTask task = it.next(); taskList.add(task); } return taskList; } public static int size(){ return queue.size(); } }
2、获取为解析的数据插入到队列中
public class SolrIndexTask extends TimerTask{ @Override public void run() { if (TaskQueue.size() == 0) { List<SolrTask> solrTaskList = generateSolrTask(); for (SolrTask task : solrTaskList) { TaskQueue.enQueue(task); } } } }
3、定时任务定时想队列中添加信息
Timer timer = new Timer(); timer.schedule(new SolrIndexTask(),0,5*24*60*60*1000);
4、做一个任务的监听,看是否又可以执行的任务
public class JobMonitor { private static Map<Long,RobotJob> jobMap = new LinkedHashMap<Long,RobotJob>(); static boolean candoMoreJob(){ //当jobMap中没有任务时,才能从队列中获取新的任务执行. if(jobMap.size()<1){ return true; }else{ return false; } } static void addJob(long id,RobotJob job){ jobMap.put(id, job); } static void removeJob(long id){ jobMap.remove(id); } static void clearAll(){ jobMap.clear(); } public static Map<Long, RobotJob> getJobMap() { return jobMap; } }
5、做一个任务中转类:当任务监听发现有新的任务时,从队列中取出任务,并将任务传递给任务处理程序,同时向任务监听类添加任务信息,阻塞新任务的执行,直到该任务执行完毕,任务监听将任务信息取出,以添加新的任务进行处理。
public class RobotJob { private SolrTask task; private String status; public RobotJob(SolrTask task){ this.task = task; } public void doJob(){ try{ JobMonitor.addJob(task.getId(), this); status = "indexing"; //任务的真正处理类. CombineJob job = new CombineJob(task.getId(),task.getWarc_file_name(),task.getWarc_file_path(),task.getRecordid()); job.doJob(); }catch(Exception e){ }finally{ JobMonitor.removeJob(task.getId()); } } public String getStatus() { return status; } }
6、完整调用为:
public static void main(String[] args) { Timer timer = new Timer(); timer.schedule(new SolrIndexTask(),0,5*24*60*60*1000); while (true) { try { if (JobMonitor.candoMoreJob()) { SolrTask task = TaskQueue.deQueue(); if (task != null) { RobotJob robotJob = new RobotJob(task); try { robotJob.doJob(); } catch (Exception e) { e.printStackTrace(); } } else { } } } } }
7、注意:在任务的真正处理类CombineJob中,我们需要循环解析每个warc文件中的每个小文件的信息,为了防止其中的一个小文件解析过程中出现错误而导致整个程序的终止,我们在进行处理小文件信息的时候开启了一个守护线程:
public abstract class SafeContainer<T> { private T object = null; private static final long MAX_MILLISTIME = 3000; protected abstract T inBox(); public SafeContainer(T object) { this.object = object; } public T execute(){ Thread thread = new Thread(){ public void run(){ try { T t = inBox(); if(t!=null){ object = t; } } catch (Exception e) { e.printStackTrace(); } } }; try { thread.start(); thread.join(MAX_MILLISTIME); thread.interrupt(); } catch (InterruptedException e) { System.out.println("-!SafeContaner Interrupted Dead Thread!"); } return object; } }
通过thread.join(MAX_MILLISTIME); 如果线程阻塞超过3秒,则中断 thread.interrupt();这样就防止了一个文件解析失败而使整个程序终止或阻塞。
以下为利用SafeContainer开启线程处理程序的方法。
public String extract_ParseText(final byte[] content, final String format) { String parseText = ""; ExtractResult ext_result = new ExtractResult(); SafeContainer<ExtractResult> container = new SafeContainer<ExtractResult>(ext_result) { @Override protected ExtractResult inBox() { return WXHFilter.extractCrawFile(content, format); } }; ext_result = container.execute(); parseText = ext_result.getContent(); return parseText; }
8、步骤7中虽然解决了通畅处理的问题,但是每个warc文件处理是非常耗时的,如何才能提高处理能力呢?
如下设想:将180服务器作为一个分发服务器,同时用多个客户端分别处理文件,每个客户端各自处理文件,处理完毕则向180服务器中获取新的任务.