背景:在开发过程中突然涉及到了多线程的处理,情况基本是这样的,见下图 ,有一个变量列“单价” 还有一个是“数量”,我需要用多线程去处理这个表中的数据,每个线程处理的是每条数据:购买单价列乘以 数量的计算,还包含对这些进行汇总。
测试代码
public BigDecimal HuiZongTotalGoodsValue(XxxPO XxxPO) {
StringBuffer stringBuffer = new StringBuffer();
NewBigdecimal totalValue = new NewBigdecimal();
long costTime = 0L;
//获取待处理集合
List<ItemPO> ItemPOS = itemService.selectItemList(XxxPO);
if (ObjectUtil.isNotEmpty(inventoryItemPOS)) {
long start = System.currentTimeMillis();
System.out.println("======>多线程启动" );
//对每一条数据分配一个线程去执行(这个线程是从线程池中拿到的)
for (ItemPO itemPO : ItemPOS) {
//对每一条数据拿一个线程去执行这个任务,具体的任务逻辑在handle()中书写
executor.execute(() -> {
stringBuffer.append(handleBussiness(itemPO, totalValue));
});
}
//判断是否所有执行任务的线程都已经执行完成
while (executor.getTaskCount() != executor.getCompletedTaskCount()){
}
long end = System.currentTimeMillis();
System.out.println("======>所有任务执行结束");
System.out.println("======>总和" + totalValue.myBigDecimal );
costTime = end - start;
System.out.println("总耗时(单位:毫秒) ======> " + costTime);
}
//多线程处理的错误记录处理(包括错误原因)
if (stringBuffer.length() > 0) {
FailLogPO failLogPO = new failLogPO();
failLogPO.setTitle(BaseConstant.DATA_SUMMARY_STATISTIC);
failLogPO.setSyncStatus(LogStatus.FAIL.name());
failLogPO.setCreateTime(DateUtil.date());
failLogService.save(failLogPO);
}
return totalValue.myBigDecimal ;
}
/**
* 业务处理,相乘后累加
**/
public static String handleBussiness(ItemPO itemPO, NewBigdecimal newBigdecimal) {
String errorStr = "";
if (ObjectUtil.isEmpty(itemPO.getTotalNum())) {
errorStr = errorStr + "id为:" + itemPO.getId() + ",数据有误,请检查数据!";
zBigdecimal.addBig(BigDecimal.ZERO);
return errorStr ;
} else if (ObjectUtil.isEmpty(itemPO.getPurchasePrice())) {
errorStr = errorStr + "id为:" + itemPO.getId() + ",数据有误,请检查数据!";
zBigdecimal.addBig(BigDecimal.ZERO);
return errorStr ;
}
zBigdecimal.addBig(BigDecimal.multiply(itemPO.getPurchasePrice(), itemPO.getTotalNum()));
return "";
}
//封装了一下BigDecimal,重写其add方法,使其add方法线程安全;
static class NewBigdecimal {
BigDecimal myBigDecimal = BigDecimal.ZERO;
synchronized public void addBig(BigDecimal add) {
myBigDecimal = myBigDecimal.add(add);
}
}
疑问:对于HuiZongTotalGoodsValue()方法中的
全局的变量:totalValue ,还有记录错误信息的全局字符串 stringBuffer.append()
*在多个线程使用的情况下都需要去考虑多个子线程如果同时用到全局变量之后,这几个子线程,对全局变量的处理或者字符串的处理,是不是线程安全的?
接下来我介绍一下我的思考过程:
(一)executor.execute()对于这个创建任务的方法中使用了两个类似于全局变量的变量(虽然不是真正的全局变量,但是相较于处理业务的handleBussiness()方法中更像是全局变量,我这里就这样叫他了,更有注意理解)
首先我需要使用加法,但是原有的BigDecimal 的加法不是线程安全的,也就是如果有多个线程同时使用一个全局类型的BigDecimal 变量时,或者使用一个BigDecimal 的方法的时候,我在这里使用的是add()方法,此时会出现两个子线程同时拿到的都是 myBigDecimal =0;然后同时add了一下,这样,只会统计上最后结束的这个子线程汇总的行的和,另外一个就会丢失,所以我这里为了保证 BigDecimal的线程安全,创建了一个NewBigDecimal,并复写了一下add方法,加上了一个同步锁synchronized ,这样就能保证所有的子线程在调用到这个add方法的时候,使用的公共变量myBigDecimal 统计的值是对的,也就是复写的add方法是线程安全的;
(二)然后对于 executor.execute()中使用的另外一个全局变量stringBuffer.append(),所有的子线程返回之后,都会调用这个append方法,因为append方法也是线程安全的,所以没问题。但是为什么这个方法是线程安全的呢 ? 为什么不适用StringBuilder?我来解释一下
对比StringBuilder的append方法已经StringBuffer的append方法:
@Override
public StringBuilder append(CharSequence s, int start, int end) {
super.append(s, start, end);
return this;
}
@Override
public synchronized StringBuffer append(CharSequence s, int start, int end)
{
toStringCache = null;
super.append(s, start, end);
return this;
}
区别只是一个在方法前加了synchronized,一个没有,所以StringBuffer是线程安全,StringBuilder是线程非安全。
(三)我当时还有个疑问:为什么处理业务函数中的errorStr字符串不用关注他呢?虽然String是线程安全的(因为他是不可变的),如果这里的这个变量不是线程安全的行吗 ?答案是可以的。
解释:因为每一个新的子线程创建任务之后,他都会调用一下这个方法,那这个方法就会为每一个子线程去创建一个新的errorStr变量,也就是这个字符串不是所有的子线程公用的,他是一个局部的,所以无需考虑这个变量是否是线程安全的。
用下面的代码解释更清晰:
public String toString(){
StringBuffer buffer = new StringBuffer();
buffer.append('<');
buffer.append(this.name);
buffer.append('>');
return buffer.toString();
}
这个代码是完全线程安全的,在方法内部定义的变量,在每个线程线程进入的时候都会创建这个局部变量!不涉及线程安全问题。通常涉及系统安全的变量一般都是成员变量! StringBuffer本身的内部实现是现场安全的!线程安全那是类本身提供的功能是安全的。即你提供插入一个字符串,那么这个字符串插入是安全的,但是要插入两个字符串,两个的顺序你来定
进一步解释:哪些需要考虑线程是否安全,哪些不需要?
线程安全是指任何时刻都只有一个线程访问临界资源。线程安全 并不是说他的一系列操作是同步的 只是对于他执行某个方法的时候不允许别的线程去改变。针对一个类来说是不是线程安全就要看,多个线程在同时在运行,这些线程可能会同时执行某个方法。但是每次运行结果和单线程执行的结果一样,那么就可以说是线程安全的。
记录到这里就结束啦,希望能解决大家在网上查不到的问题。一起进步,有什么不足之处,请多多指教~
希望小伙伴能学到一点东西, 路过的点个赞或点个关注呗~