任务调度之队列(queue)

本文介绍了Java中队列的应用,特别是阻塞队列和非阻塞队列的使用,如LinkedBlockingQueue和ConcurrentLinkedQueue。队列在数据库操作、网络爬虫解析任务调度中起到关键作用。通过队列,程序可以高效地管理待处理的warc文件,避免因单个文件解析错误导致整个程序终止。同时提出了并发处理文件以提升效率的思路。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

队列像栈一样,都是表,队列的特点是在队列的一端(队尾)进行插入而在另一端(对头)进行删除操作,采取的是先进先出的策略。

阻塞队列(同步):
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服务器中获取新的任务.
#include #include #include //队列最大长度 #define MAX_QUEUE 1024 //偷懒,就用静态队列了 static int mQueue[MAX_QUEUE]; //队列插入 void InsertData(int **Front, int **Rear) { if (*Rear + 1 == *Front && (*Rear + 1 - MAX_QUEUE != *Front)) { //当队列数据已满,返回 puts("Queue Size Overflow!\n"); return; } else if (*Rear - mQueue > MAX_QUEUE) { //实现的是类似循环队列,但由于是静态线性队列(数组) //而不是用链表来实现的,所以到静态队列(数组)尾部,尾指针自动指向(数组)头部 *Rear = mQueue; } puts("Input Data:"); scanf("%d", *Rear); //输入数据后,尾指针后移 *Rear += 1; } //从头指针删除一个队列中的数据 void DeleteData(int **Front, int **Rear) { if (*Front == *Rear) { //头指针尾指针重合,队列空,不能删除,返回 puts("Queue Empty!\n"); return; } else if (*Front - mQueue > MAX_QUEUE) { //参考 Rear *Front = mQueue; } //从头指针删除一个数据 *Front += 1; } //显示队列数据 void ShowData(int **Front, int **Rear) { int *temp; for (temp=*Front; temp!=*Rear; temp++) { printf("%d --> ", *temp); } puts("\n"); } void usage(void) { puts("1. Insert Data"); puts("2. Delete Data"); puts("3. Show Data"); } int main(int argc, char **argv) { //头指针,尾指针 //队列的一个特性 First in first out FIFO int *pFront, *pRear; int op_code; //初始化队列,头指针和尾指针此时指向的地址相同 pFront = pRear = mQueue; while (1) { usage(); scanf("%d", &op_code); switch (op_code) { case 1: printf("%p\n", pFront); printf("%d\n", *pFront); InsertData(&pFront, &pRear); break; case 2: DeleteData(&pFront, &pRear); break; case 3: ShowData(&pFront, &pRear); break; default: break; } } return 0; }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值