博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
电影票项目之Worker多线程
阅读量:4557 次
发布时间:2019-06-08

本文共 6789 字,大约阅读时间需要 22 分钟。

前一篇项目总结里,说了电影票项目的worker的界面控制。这里来说一下多线程的运行,并且这里当时开发的还出现过一个问题。所以说,多线程,也最容易出问题。

 

场景:取一百部电影近3天的排期信息,创建一个线程池,多线程去请求数据

代码:2个类,一个配置文件,采用模拟方式,构造数据,模拟执行库的update等

错误的方式其实就在这一段代码里:

 

for (CinemaDetail cinemaDetail : cinemaDetails) {    for (int i = 1; i <= 3; i++) {// 一次循环取一家影院一天的排期        System.out.println(Thread.currentThread().getName() + "取影院ID:" + cinemaDetail.getCinemaId() + "第" + i + "天的排期");         cinemaDetail.setIssueDate(i);        executorService.execute(new getDataTask(cinemaDetail));        while (executorService.getQueue().size() >= (queueSize - 3)) {            // 等待队列有空位置,任务先创建corePoolSize大小的线程,再往队列中压,超队列再创建线程直到maxPoolSize            // executorService.getActiveCount(),由于通过work.isLock判,不在一个线程中,线程对象被创建,不表示马上会调度,使isLock返回TRUE,会有并发问题            // executorService.getPoolSize(),等待超过空闲,线程回收后才会变化            System.out.println("取排期有几积压,在排队");            try {                Thread.sleep(500);            } catch (InterruptedException e) {                // 不处理            }        }    }}

 

 

眼尖的同学可能已经看到问题所在了。

先来剖析一下这个主要的业务处理类吧

在Spring初始bean时,先初始一下线程池:

public void init() {    // 创建一个固定大小的线程池    executorService = new ThreadPoolExecutor(threadSize, maxThreadSize, idletime, TimeUnit.SECONDS, new LinkedBlockingQueue
(queueSize));}

这里有文档介绍ThreadPoolExecutor

 

在这个bean创建的时候,静态初始100条影院数据

 

static {        System.out.println("开始创建:---------");        long start = System.currentTimeMillis();        cds = new ArrayList
(); for (Integer i = 1; i <= 100; i++) { CinemaDetail cd = new CinemaDetail(); cd.setCinemaId(i); cd.setCinemaName(i.toString()); cds.add(cd); } System.out.println("结束创建,总耗时:" + (System.currentTimeMillis() - start)); }

 

 

我下面展示的所以输出都是把log替代成直接输出到控制台,具体要执行的方法:

 

public void execute() {        if (executorService == null) {        throw new NullPointerException("executorService is null,please call init()!");    }        boolean flag = true;    while (flag) {         List
cinemaDetails = getAllCinemas(iCinemaId, pageSize); if (CollectionUtils.isEmpty(cinemaDetails)) { flag = false; System.out.println(Thread.currentThread().getName() + "无影院,本轮同步结束"); break; } // 记录下次循环需要执行的位置 iCinemaId = iCinemaId + pageSize; System.out.println(Thread.currentThread().getName() + "取所有影院排期信息===task===>开始,iCinemaId" + iCinemaId); for (CinemaDetail cinemaDetail : cinemaDetails) { for (int i = 1; i <= 3; i++) {// 一次循环取影院每一天的排期 System.out.println(Thread.currentThread().getName() + "取影院ID:" + cinemaDetail.getCinemaId() + "第" + i + "天的排期"); cinemaDetail.setIssueDate(i); executorService.execute(new getDataTask(cinemaDetail)); while (executorService.getQueue().size() >= (queueSize - 3)) { System.out.println("取排期有几积压,在排队"); try { Thread.sleep(500); } catch (InterruptedException e) { // 不处理 } } } } while (executorService.getActiveCount() > 0) { // 本次时间任务跑完后,才能处理下次时间任务的调度 log.debug("取排期等待本次投注任务调度完成"); try { Thread.sleep(500); } catch (InterruptedException e) { // 不处理 } } } System.out.println("取所有影院排期信息===task===>结束");}

 

 

然而executorService.execute(new getDataTask(cinemaDetail));所执行的就是一个模拟处理,这里只计数和打印信息,看一下代码

 

public class getDataTask implements Runnable {    private CinemaDetail cinemaDetail;        public getDataTask(CinemaDetail cinemaDetail) {        this.cinemaDetail = cinemaDetail;    }        public void run() {        executeData(cinemaDetail);     }}

 

 

具体执行如下:

private void executeData(CinemaDetail cinemaDetail) {    count.addAndGet(1);    System.out.println(Thread.currentThread().getName() + "当前执行数:" + count.get() + "影院ID:" + cinemaDetail.getCinemaId() + "排期信息:第"            + cinemaDetail.getIssueDate() + "天");        try {        Thread.sleep(500);//模拟处理业务逻辑    } catch (InterruptedException e) {        // 不处理    }}

那再来看一下配置文件,具体配了些啥,为了简单点,我就没有用上次说的Scheduler了,直接用随应用启动而启动,启动时间是每59秒一次。

execute
0/59 * * * * ?

打包,启动程序,执行结果,一看,咋就不对呢?

全取到的是第三天的结果,就是对第三天的结果进行了三次处理

 

这里也模拟出来了,需要排队等待的请况:

执行结束时的结果

这里会有quartz worker 2的原因是我把配置文件里的concurrent属性配成了true,请看配置处对此属性的注释

从运行结果来看,基本不会执行取第一天,第二天的数据,全执行的是第三天的数据(这时的脑袋是短路了,根本不会先问题),多线程造成的?

那我们用同步来试试?

synchronized (cinemaDetail) {    cinemaDetail.setIssueDate(i);    executorService.execute(new getDataTask(cinemaDetail));}

如果没有发现问题的本质,你同步也是不行的。请看结果:

冷静下来,再看,你会发现,其实不是线程公用变量之类的事情造成的

 

for (CinemaDetail cinemaDetail : cinemaDetails) {    //一次循环,取1家影院近3天排期数据    for (int i = 1; i <= 3; i++) {        // 一次循环取一家影院一天的排期        cinemaDetail.setIssueDate(i);        executorService.execute(new getDataTask(cinemaDetail));    }}

 

 

再来仔细看一下这个for循环,这里我去掉了一些不必要的信息,这是两个循环引起的,再认真看一下注释。在第二个循环来,我们还是用的同一个对象(引用)。那我们每次做的set操作,都是针对同一个引用来的。而当多线程去执行的时候,当然只认现在这个引用的状态,在这里也就是当前的属性(最后一次修改为3)。想明白这里之后,改起来应该就方便了。有几种方式可以选择,可以clone一个对象,或者新建一个对象。

这里有文档对clone的介绍:

 

如果采用clone的话,CinemaDetail这个类需要实现Cloneable接口,如下:

public class CinemaDetail implements Cloneable{    public CinemaDetail clone() {        CinemaDetail cd;        try {            cd = (CinemaDetail) super.clone();            return cd;        } catch (CloneNotSupportedException e) {            // TODO Auto-generated catch block            e.printStackTrace();        }        return null;    }}

 

然后for循环里进行相应的修改:

 

for (CinemaDetail cinemaDetail : cinemaDetails) {    //一次循环,取1家影院近3天排期数据    for (int i = 1; i <= 3; i++) {        CinemaDetail cinemaDetailClone = cinemaDetail.clone();        // 一次循环取一家影院一天的排期        cinemaDetailClone.setIssueDate(i);        executorService.execute(new getDataTask(cinemaDetailClone));    }}

 

如果采用new对象的话,相应修改如下:

for (CinemaDetail cinemaDetail : cinemaDetails) {    //一次循环,取1家影院近3天排期数据    for (int i = 1; i <= 3; i++) {        // 一次循环取一家影院一天的排期        CinemaDetail cinemaDetailNew = new CinemaDetail();        //有多少属性,就复制多少属性。。。如果属性多就麻烦了        cinemaDetailNew.setCinemaId(cinemaDetail.getCinemaId());        cinemaDetailNew.setCinemaName("cinemaName");        cinemaDetailNew.setIssueDate(i);        executorService.execute(new getDataTask(cinemaDetailNew));    }}

 

这两种方式,都可以正常的运作了。

看一下运行的结果吧

解决了之后,心里还是美滋滋的,项目也正常往下进行着。

总结:

有多线程的使用时,全局变量的使用需要谨慎

Synchronized也并不是万能的,首先应明白原因

一个对象在两重以上的循环里处理时,需要注意

多线程ThreadPoolExecutor创建的使用

对象的clone,需要实现cloneable

SpringQuartz的配置熟练使用

 

总结中,有些是针对这次的问题的,有些是针对这次的内容的。

 

转载于:https://www.cnblogs.com/bbsno1/p/3255890.html

你可能感兴趣的文章
M51文件注释
查看>>
关于临界资源访问互斥量的死锁问题
查看>>
django-view层
查看>>
异步加载JS的方法。
查看>>
golang-gorm框架支持mysql json类型
查看>>
【tool】白盒测试
查看>>
图论其一:图的存储
查看>>
20180923-WebService
查看>>
z变换
查看>>
Python - 静态函数(staticmethod), 类函数(classmethod), 成员函数
查看>>
Spring基础2
查看>>
【灵异短篇】这个夜晚有点凉
查看>>
一点小问题
查看>>
pytest 10 skip跳过测试用例
查看>>
MVC身份验证及权限管理
查看>>
It was not possible to find any compatible framework version
查看>>
关于8.0.15版本的mysql下载与安装
查看>>
Redis主从复制看这篇就够了
查看>>
洛谷 P1202 [USACO1.1]黑色星期五Friday the Thirteenth 题解
查看>>
(4.20)SQL Server数据库启动过程,以及启动不起来的各种问题的分析及解决技巧...
查看>>