创新实训(37)——使用多线程进行相似文章的预计算

前言

在同学的定时抓取博客的任务做好之后,进行的抓取的尝试,在抓取文章之后,本来计划的用lucene建立倒排索引,然后为每一篇抓取到的文章在所有的文章中,利用倒排索引计算TF-IDF,然后计算最相似的文章。当时收录了3500篇文章,对于每一篇文章都需要和其他的三千多篇文章计算相似度,非常的慢,所以想到使用多线程分别对文章进行相似文章的计算,然后提高运行的速度。

使用java的Thread类,手动创建线程

每一个线程,都会读取数据库文章列表中的一部分文章,通过传入的limit和offset来判断,然后这个线程就专心处理这一部分数据。

 /**
     * 进行计算的线程
     */
    public class MyThread extends Thread {
        public int limit;
        public int offset;
        public int id;
        List<Article> articles;
        public MyThread(int limit ,int offset,int id) {
            this.limit = limit;
            this.offset = offset;
            this.id = id;
            articles = articleDao.selectArticleList(limit,offset);
        }
        // 重写运行方法
        @SneakyThrows
        @Override
        public void run() {

            //计算文章的相似文章 存入redis
            for(Article article :articles)
            {
                int articleId = article.getId();
                System.out.println(id+"号线程"+"正在计算"+articleId+"号文章的相似文章");
                List<Integer> similarityArticleList = luceneUtil.searchSimilarity(articleId,SIZE);
         System.out.println(articleId+"的相似文章为"+similarityArticleList);
                //移除旧的相似文章
                Set<String> removeList = redisUtils.setGetValues(SET_KEY+String.valueOf(articleId));
                for(String str : removeList)
                {
                    redisUtils.setremove(SET_KEY+String.valueOf(articleId),str);
                }
                //加入新的相似文章
                for (int i :similarityArticleList)
                {
                    if(i != articleId)
                    {
                        redisUtils.setAdd(SET_KEY+String.valueOf(articleId) , String.valueOf(i));
                    }

                }
            }
        }
    }

主线程负责对数据进行分页,然后创建子线程,给每个子线程不同的页,分别进行计算,然后将相似文章的计算结果提前存入redis

    /**
     * 每次抽取完成,并且建立索引之后,都计算每篇文章10个相似的文章,存入redis,方便取用
     */
    public void init() throws IOException, org.apache.lucene.queryParser.standard.parser.ParseException, InterruptedException {
        //初始化
        luceneUtil.postConstruct();
        //从第几页开始 默认从0开始
        int curPage=0;
        //每次写入两条
        int size=500;
        //数据库中的文章总条数
        int articleNumbers = articleDao.selectArticleCount();
        int totalPage = articleNumbers/size;
        System.out.println("总条数"+articleNumbers);
        System.out.println("总页数"+totalPage);
        List<MyThread> threads = new ArrayList<>();
        while (curPage<=totalPage)
        {
            MyThread m1 = new MyThread(size,curPage*size,curPage);
            m1.start();
            threads.add(m1);
            System.out.println("当前页数"+curPage);
            curPage++;
        }
        // 将计算的线程合并到主线程中  等待子线程全部执行结束 主线程才结束
        for (int i = 0; i < threads.size(); i++) {
            threads.get(i).join();
        }
        System.out.println("执行结束");
    }

问题:
刚开始的时候,主线程new完新的线程,然后调用了start()方法,将线程run了起来,之后就没在处理,最后发现主线程不会等待子线程结束,就直接会运行完整个方法。
是由于我在start子线程之后,没有对子线程进行join,join会让主线程等在子线程结束之后,在结束主线程
所我建了个List存储线程,最后进行join操作,让主线程等待。

 // 将计算的线程合并到主线程中  等待子线程全部执行结束 主线程才结束
        for (int i = 0; i < threads.size(); i++) {
            threads.get(i).join();
        }

最后的执行速度很快
在这里插入图片描述
不到5分钟计算完毕,才开了八个线程。

但是,自己创造线程也有一些问题:
线程是稀缺资源,不能频繁的创建。自己创建线程,很可能造成一些资源的浪费,所以好像更加推荐使用线程池。

SpringBoot配置和使用线程池

(1)线程池原理
在这里插入图片描述

threadPool.execute(new Job());

execute的执行流程:
(1)获取当前线程池的状态。
(2)当前线程数量小于 coreSize 时创建一个新的线程运行。
(3)如果当前线程处于运行状态,并且写入阻塞队列成功。
(4)双重检查,再次获取线程状态;如果线程状态变了(非运行状态)就需要从阻塞队列移除任务,并尝试判断(5)线程是否全部执行完毕。同时执行拒绝策略。
(6)如果当前线程池为空就新创建一个线程并执行。
(7)如果在第三步的判断为非运行状态,尝试新建线程,如果失败则执行拒绝策略。

SpringBoot 使用线程池:
(1)首先是配置线程池的bean交给spring 管理:

@Configuration

public class TaskExecutePool {

    @Bean(name ="threadPoolA")
    public ThreadPoolTaskExecutor TaskAsyncPool() {
        System.out.println("线程池已创建");
        ThreadPoolTaskExecutor executor =new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(15);

        executor.setMaxPoolSize(20);

        executor.setQueueCapacity(100);

        executor.setKeepAliveSeconds(60);

        executor.setThreadNamePrefix("Pool-A");

        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());

        executor.initialize();

        return executor;

    }


}

corePoolSize:表示线程池核心线程,正常情况下开启的线程数量。

queueCapacity:当核心线程都在跑任务,还有多余的任务会存到此处。

maxPoolSize:如果queueCapacity存满了,还有任务就会启动更多的线程,直到线程数达到maxPoolSize。如果还有任务,则根据拒绝策略进行处理。

(2)使用
使用线程只需要在执行方法上加上注解,同时该方法的类必须被定义为bean,交由spring管理。
可以在类上使用注解@Component、@Service等
在这里插入图片描述

 @Async(value="ThreadPoolA")
    public void calculateSimilarity(int curPage,int size) throws IOException, ParseException {


            List<Article> articles = articleDao.selectArticleList(size, curPage * size);
//            计算文章的相似文章 存入redis
            for (Article article : articles) {

                int articleId = article.getId();
                System.out.println("正在计算" + articleId + "号文章的相似文章");
                List<Integer> similarityArticleList = luceneUtil.searchSimilarity(articleId, SIZE);
                System.out.println(articleId + "号相似文章味为"+similarityArticleList);
                //移除旧的相似文章
                Set<String> removeList = redisUtils.setGetValues(SET_KEY + String.valueOf(articleId));
                for (String str : removeList) {
                    redisUtils.setremove(SET_KEY + String.valueOf(articleId), str);
                }
//                加入新的相似文章
                for (int i : similarityArticleList) {
                    if (i != articleId) {
                        redisUtils.setAdd(SET_KEY + String.valueOf(articleId), String.valueOf(i));
                    }

                }
                 System.out.println("当前页数" + curPage);

        }
    }
    /**
     * 每次抽取完成,并且建立索引之后,都计算每篇文章10个相似的文章,存入redis,方便取用
     */
    public void init() throws IOException, org.apache.lucene.queryParser.standard.parser.ParseException, InterruptedException {
        //初始化
        luceneUtil.postConstruct();
        //从第几页开始 默认从0开始
        int curPage=0;
        //每次写入两条
        int size=150;
        //数据库中的文章总条数
        int articleNumbers = articleDao.selectArticleCount();
        int totalPage = articleNumbers/size;
        System.out.println("总条数"+articleNumbers);
        System.out.println("总页数"+totalPage);
        while (curPage<=totalPage)
        {
            calculateSimilarity(curPage,size);

            System.out.println("当前页数"+curPage);
            curPage++;
        }


        System.out.println("执行结束");
    }

最后的执行结果为:
在这里插入图片描述
虽然速度慢了写,但是他会自动调度资源,并且占用的内存,cpu都少,不会浪费资源。

SpringBoot配置和使用线程池(方法2)

(1)配置线程池

  @Bean(name ="threadPoolB")
    public ExecutorService ThreadPool() {
        ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
                .setNameFormat("consumer-queue-thread-%d").build();
        ExecutorService pool = new ThreadPoolExecutor(15, 20, 0L, TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<Runnable>(10),namedThreadFactory,new ThreadPoolExecutor.AbortPolicy());
        return pool ;

    }

(2)执行任务

 /**
     * 每次抽取完成,并且建立索引之后,都计算每篇文章10个相似的文章,存入redis,方便取用
     */
    public void init() throws IOException, org.apache.lucene.queryParser.standard.parser.ParseException, InterruptedException {
        //初始化
        luceneUtil.postConstruct();
        //从第几页开始 默认从0开始
        int curPage=0;
        //每次写入两条
        int size=300;
        //数据库中的文章总条数
        int articleNumbers = articleDao.selectArticleCount();
        int totalPage = articleNumbers/size;
        System.out.println("总条数"+articleNumbers);
        System.out.println("总页数"+totalPage);
        CountDownLatch countDownLatch = new CountDownLatch(totalPage+1);
        while (curPage<=totalPage)
        {

            MyThread m1 = new MyThread(size,curPage*size,curPage);
            consumerQueueThreadPool.execute(m1);

/
            System.out.println("当前页数"+curPage);
            curPage++;
        }
        try {
            //保证之前的所有的线程都执行完成,才会走下面的;
            countDownLatch.await();
            // 这样就可以在下面拿到所有线程执行完的集合结果
        } catch (Exception e) {
           System.out.println("阻塞异常");
        }

 

        System.out.println("执行结束");
    }

    /**
     * 进行计算的线程
     */
    public class MyThread extends Thread {
        public int limit;
        public int offset;
        public int id;
        List<Article> articles;
        public MyThread(int limit ,int offset,int id) {
            this.limit = limit;
            this.offset = offset;
            this.id = id;
            articles = articleDao.selectArticleList(limit,offset);
        }
        // 重写运行方法
        @SneakyThrows
        @Override
        public void run() {
//            System.out.println("当前是"+id+"号线程");
//            System.out.println("开始计算limit="+limit+" offset"+offset +"的数据");
            //计算文章的相似文章 存入redis
            for(Article article :articles)
            {
                int articleId = article.getId();
                System.out.println(id+"号线程"+"正在计算"+articleId+"号文章的相似文章");
                List<Integer> similarityArticleList = luceneUtil.searchSimilarity(articleId,SIZE);
//                System.out.println(articleId+"的相似文章为"+similarityArticleList);
                //移除旧的相似文章
                Set<String> removeList = redisUtils.setGetValues(SET_KEY+String.valueOf(articleId));
                for(String str : removeList)
                {
                    redisUtils.setremove(SET_KEY+String.valueOf(articleId),str);
                }
               //加入新的相似文章
                for (int i :similarityArticleList)
                {
                    if(i != articleId)
                    {
                        redisUtils.setAdd(SET_KEY+String.valueOf(articleId) , String.valueOf(i));
                    }
                }
            }
        }
    }

(3)让子线程在执行完之前,阻塞主进程

        CountDownLatch countDownLatch = new CountDownLatch(totalPage+1);

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
     try {
            //保证之前的所有的线程都执行完成,才会走下面的;
            countDownLatch.await();
            // 这样就可以在下面拿到所有线程执行完的集合结果
        } catch (Exception e) {
           System.out.println("阻塞异常");
        }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值