您的位置:首页 > 编程语言 > Java开发

使用THREAD_POOL.execute引发一次神奇的多线程问题

2017-10-25 15:55 676 查看
说先说下问题吧,这样会比较具体。首先在我的循环中for (MovieOrderDetail movieOrderDetail : list) 获得了5条数据,对应的ID分别为

('1001','1002','1003','1004');

好,现在将值传递给movieOrder.setId(movieOrderDetail.getMovieOrderId());调用THREAD_POOL.execute(new OrderJobService(cdl, movieOrder));多线程去执行,

进入OrderJobService方法,通过执行 run() 方法,发现了神奇的问题,LogMgr.sysInfoLog("########"+order.getId());打印出来的ID出现了重复,(有时候2条重复,有时候1条重复)通过线程ID对比发现,同一个线程ID,[thread-5-1] 输出的ID为1001,[thread-5-2] 输出的ID为1002,[thread-5-3] 输出的ID为1001,

[thread-5-4] 输出的ID为1004,可是我List的数据是不重复的,打印也发现,数据都是不重复的。那为什么通过THREAD_POOL.execute(new OrderJobService(cdl, movieOrder));传递到run() 方法后就重复,而在线程类的OrderJobService构造方法打印也不重复public OrderJobService(CountDownLatch cdl, MovieOrder order) {
this.cdl = cdl;
this.order = order;
},就是这个方法,打印参数传递过来的也是唯一的,但是进入run()方法在Get这个ID就出现重复了。

1002被执行两次,

程序很简单

public class ArousePayOrderJobHandler extends IJobHandler {

private ExecutorService THREAD_POOL = null;

public static final int ROWS = 2000;

@Autowired
private IMovieOrderDetailService movieOrderDetailService;

@Override
public JobHandleStatus execute(String... params) throws Exception {
long startTime = System.currentTimeMillis();
LogMgr.sysInfoLog("【----- 唤起支付订单定时任务-----】开始!");
try {
THREAD_POOL = Executors.newFixedThreadPool(20);
// 查询唤起过支付的订单
MovieOrderDetailVO orderDetail = new MovieOrderDetailVO();
orderDetail.setStatusList(Arrays.asList(IEnumType.MovieOrderStatus.CREATE.getCode(),
IEnumType.MovieOrderStatus.PAY_ING.getCode()));
orderDetail.setIsArousePay(IEnumType.ArousePay.IS_ArousePay.getCode());
Date currentTime = new Date();
orderDetail.setStartTime(DateUtil.addMinute(currentTime, -40));
orderDetail.setEndTime(currentTime);
orderDetail.setSize(ROWS);

List<MovieOrderDetail> list = movieOrderDetailService.selectByTime(orderDetail);
if (list == null || list.isEmpty()) {
LogMgr.sysInfoLog("40分钟内没有唤起支付页面的订单!");
} else {
MovieOrder movieOrder = new MovieOrder();
CountDownLatch cdl = new CountDownLatch(list.size());
for (MovieOrderDetail movieOrderDetail : list) {

movieOrder.setId(movieOrderDetail.getMovieOrderId());
LogMgr.sysInfoLog("KKKKK"+movieOrder.getId());
THREAD_POOL.execute(new OrderJobService(cdl, movieOrder));
}
cdl.await();
}
THREAD_POOL.shutdown();
} catch (Exception e) {
LogMgr.writeErrorLog("【----- 唤起支付订单定时任务-----】 异常!", e);
}

return JobHandleStatus.SUCCESS;
}

}

public class OrderJobService implements Runnable {
private final CountDownLatch cdl;
private final MovieOrder order;
private static IOrderHandleService orderHandleService;

static {
orderHandleService = SpringContextUtil.getBean(IOrderHandleService.class);
}

public OrderJobService(CountDownLatch cdl, MovieOrder order) {
this.cdl = cdl;
this.order = order;
}
@Override
public void run() {
try {
LogMgr.sysInfoLog("########"+order.getId());
orderHandleService.orderHandle(order.getId());

} catch (Exception e) {
LogMgr.writeErrorLog("处理异常!", e);
} finally {
//LogMgr.sysInfoLog("处理结束!");
cdl.countDown();
}
}

}

代码逻辑不复杂,查询数据库返回一个List,交给多线程去执行。可是问题是本机不会出现,测试服务器就出现,尝试了很多办法都没解决,包括替换Tomcat,替换Jdk,都不行,最后发现,肯定还是程序那,出了问题,安心坐下来排查,最后找到了,问题出在MovieOrder 这个对象创建上了,看代码发现,这个对象是在for循环外被创建的,在循环里去movieOrder.setId(movieOrderDetail.getMovieOrderId()); 然后交给THREAD_POOL.execute,如果把对象初始化放倒循环里,就没事了,那么问题来了,为什么会出这样的问题呢?

当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,但是这个成员变量被多线程引用,而对象只有一个,那么引用为什么会乱呢?首先垃圾回收算法大致有三种,如:
标记-复制 它将可用内存容量划分为大小相等的两块,每次只使用其中的一块。当这一块用完之后,就将还存活的对象复制到另外一块上面,然后在把已使用过的内存空间一次理掉。
标记-清理 标记清除算法分为“标记”和“清除”两个阶段:首先标记出需要回收的对象,标记完成之后统一清除对象。
标记-整理 标记操作和“标记-清理”算法一致,后续操作不只是直接清理对象,而是在清理无用对象完成后让所有 存活的对象都向一端移动,并更新引用其对象的指针。

通过分析,我的Tomcat采用G1的垃圾回收器,G1算法将堆划分为若干个区域,G1收集器通过将对象从一个区域复制到另外一个区域,完成了清理工作。每个线程都有本地分配缓冲区,如果对象在一个共享的缓冲区内被分配,需要采用同步机制来管理这些空间内的空闲指针,那么问题会不会出现在线程间引用变量的传递后,被分配到同一个缓冲区,并且指针都被引用同一个变量呢?我的理解是这样,希望有别的大牛给出指正。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息