基于任务队列的多线程

前传:

接到一个新任务,就是pdf文件上传后要转成图片

思路很简单

 

伪代码:

 

        //获取上传文件

        File file=new File(“路径”);

        //引用pdf工具类把pdf转成图片

        Img img=PDFUtil.toImg(file);

       

 

后来…需求改了….

要传一个压缩包…是多个文件,也简单

 

        //获取上传文件

        FileRar file=new File(“路径”);

        //解压获取所有文件对象

        List<File> files= RarUtil.getFiles(Rarfile);

        for (File file : files) {

            //引用pdf工具类把pdf转成图片

            Img img=PDFUtil.toImg(file);

        }

 

加几行代码也过了

 

在后来……用户吼道~文件上传太慢,检查一下…文件上传一个压缩包有几百个文件,而且文件都较大,处理速度慢,而且异常后要从来…用户抓狂了~

 

业务: 这个问题一定要解决

PM: 这个一定能解决

程序猿: 香蕉在哪里~

 

一般web的文件上传业务和逻辑基本都放一起,因为这样做简单省事,当然在不考虑文件大小和处理速度的时候~

这里大量的文件和冗长的处理时间以及异常确实不太合适,这个时候,就需要后台处理了,程序猿吃着香蕉开始做方案

 

方案:

前提需求:这部分肯定得后台做与文件上传分离,然后就是由什么来触发这个文件的处理,异常如何处理,处理时间如何保障

 

方案一

做定时任务,在表里加个字段,一天执行一次或多次(这样做会显得非常白痴,延时太高,而且不好定时间,要同时考虑到执行时间段的问题[ps:全世界人都觉得晚上12点服务器最轻松,所以所有定时任务都是晚上12点……])

 

~~~不用看了,这个马上毙掉

 

方案二:

后台启用线程(也可以另外写一个单独的程序,不过太麻烦了,成本上划不来),间隔(1分钟或者10分钟)读取数据库,执行任务

这样确实延时小了,上传与处理也分离开了,虽然有增加数据库查询的负担,不过可以容忍(每分钟一次的查询所消耗的资源还是非常小的)

 

总的来说,这个方案不错,不过对于有完美主义倾向的处女座来说..(处女座程序猿吼道:什么一分钟一次查询,这不行,数据库连接断了怎么办,晚上没人穿文件还得继续查?不行,不科学~)

所以方案二也被毙掉了

 

方案三

后台起一个现成,前台用户上传文件后马上触发事件,执行修改.

有几个关键点:

Web程序如何触发和控制这个任务线程,每次都是new一个线程,然后在.run()

再就是springmvc是多线程的,如果用户量很大,会不会演变成1000个用户起1000个线程,如果真是这样,线程中如果不小心在new几个对象或者集合……内存怎么办,还有线程并发问题,共享问题(其实没那么多问题,这只是猜测,具体请深入了解spring的对象管理机制)

是不是使用线程池就不会有问题了?

线程池可以减少new线程的成本,但是线程的数量问题依旧不能得到解决(虽然现在的用户量肯定暴不了内存)

 

理想情况:

所有用户共享一个单独的任务处理线程,有任务就处理,没任务就阻塞

 

 

 

好吧,前面全是废话

现在要实现功能:

1. springmvc可以使用的后台线程

2. 该线程是基于任务的,阻塞的

 

So…很简单..所以代码也很简单

 

首先定义一个基类:

 

具体的说来,这个方法很简单,是一个处理任务的基类,实现一个线程,判断队列中是否有数据,没有数据就阻塞,有数据则调用实现类的process()方法处理任务

 

(要集成到spring中,所以前面的注解必须要写清楚, prototype代表这个类可以有多个实例先这样配置,用不用得到以后在推论)

 

BlockingQueue 这是一个阻塞队列,读取和写入的方式请参考< http://blog.csdn.net/cyp900509/article/details/40895695 >

 

 

@Component 

@Scope("prototype"

public abstractclass BaseTask<T>  implements  Runnable {

 

    protected BlockingQueue<T> queue;// 任务队列

 

    public BlockingQueue<T> getQueue() {

        return queue;

    }

 

    public void setQueue(BlockingQueue<T> queue) {

        this.queue = queue;

    }

 

    @Override

    public void run() {

        try {

            for (;;) {

                Thread.sleep(1000);

                Tt = queue.take();

                process(t);

            }

        }catch(Exception e) { 

            e.printStackTrace();

        }

 

    }

 

    /**

     * 业务处理逻辑

     *

     * @param t

     */

    protected abstract void process(T t);

 

}

 

 

这个就是将来要传入 BaseTask<T>中的 T ,额看不懂请参考…算了,别参考了,还是换个行业吧…

 

public class TaskBean1 {

    private String name;

 

    public String getName() {

        return name;

    }

 

    public void setName(String name) {

        this.name = name;

    }

}

 

任务处理类

这个就是具体执行任务的类,线程接到任务后,真正处理的逻辑在这

 

public class Task1 extends BaseTask<TaskBean1> {

 

    @Override

    protected void process(TaskBean1 t) {

        System.out.println(t.getName());

    }

 

}

 

 

Ok,父类有了,线程有了,实现也有了,那调用试试

 

 

public classMain {

    private static BlockingQueue<TaskBean1> queue =new ArrayBlockingQueue<TaskBean1>(10);

 

    public static void main(String[] args) {

        //这个是java自带的线程池

        ExecutorService service = Executors.newCachedThreadPool();

 

        //先实例化一个任务对象

        Task1t1 = newTask1();

        //把队列和任务对象绑定在一起 

        //这里多说一句,最好应该是在构造函数中直接传进去,但是springmvc中对这个支持不是很好,所以先粗暴一把,以后在想办法

        t1.setQueue(queue); 

 

        //启动这个线程

        service.submit(t1);

 

        try {

            //线程有了,接下来不能让线程闲着啊,拼命建任务啊

            //任务一

            TaskBean1  b1 = new TaskBean1();

            b1.setName("gogogo~");

            //任务二

            TaskBean1 b2 = new  TaskBean1();

            b2.setName("tototo~");

            //任务??

            TaskBean1 b3 = new  TaskBean1();

            b3.setName("ououou~");

            //任务??

            TaskBean1  b4 = new  TaskBean1();

            b4.setName("aoaoao~");

           

            //任务有了,丢到一起,之后的事就让任务线程去忙吧,吼吼

            queue.put(b1);

            queue.put(b2);

            queue.put(b3);

            queue.put(b4);

        }catch(InterruptedException e1) {

            e1.printStackTrace();

        }

 

        try {

            Thread.sleep(5000);

        }catch(Exception e) {

            // TODO: handle exception

            e.printStackTrace();

        }

    }

}

 

 

 

就这样一个简单的基于任务的线程就搞定了,为了证明可用性…接下来处理pdf吧~~

 

 

第一步 把任务处理逻辑起来

1.  继承BaseTask<T>类,并且确定<T>的类型

2.  实现process()方法

 

@Component

public class ImgProcessTask extends BaseTask<StdFiles> {

 

    @Override

    protected void process(StdFiles t) {

        try {

            // 获取文件路径

            Filefile = newFile(“”);

            doBufferedImage(file); //生成预览图

                 

        } catch (Exception e) {

            log.error("文件转图片失败,文件信息:{}", t.toString());

        }

    }

 

第二步: 在mvc中加入这个任务机制

 

    //引入实现的类(必须是spring托管的,否则会是null)

    @Autowired

    private ImgProcessTask it;

    //初始化任务队列(全球共享)

    public static BlockingQueue<StdFiles>  IMGPROCESS_QUEUE =new ArrayBlockingQueue<StdFiles>(1000);

   

     // 初始化线程池

     ExecutorService  service = Executors.newCachedThreadPool();

    // PostConstruct 是告诉spring初始化类后要调用这个方法

    @PostConstruct

    public void init(){ 

        System.out.println("~~~~~~~~~~~~~~~~~init~~~~~~~~~~~~~~~~~~~~~~~~~~~~");

        it.setQueue(IMGPROCESS_QUEUE);;

         service.submit(it);

        System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");

    }

 

 

第三部:

在web的server中把任务加到队列里面去,

 

 

pabulic void rarProcess(StdFiles t) {

 

    //实例文件类

StdFilesfmodel=………

    //文件转图片处理

    boolean imgret=common.IMGPROCESS_QUEUE.offer(fmodel);  // offer方法可以判断任务是否加载到队列中去

    if (!imgret) {

        logger.warn("文件加入处理列表失败,文件信息:{}",fmodel.toString());

}

}

 

//最后启动试试~~

 

 

总结:

做外包也好做啥也好,都无所谓,努力做最好的自己就好.不用总是把自己伪装成成受害者~额,虽然我做的也不是很好…共勉吧~


例子: http://download.csdn.net/detail/cyp900509/8141915

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值