Thread的join方法使用
一、Thread的join方法使用
1.业务背景
从java调用多个存储过程,其调用的存储过程并无先后顺序要求。但存储过程需都执行完成后,代码才能执行后面的逻辑。
2.代码示例
public void joinStudy(){
// 假设要五个存储过程需要调用
String[] params = {"A","B","C","D","E"};
// 存储线程,后续对线程进行遍历
List<Thread> list = new ArrayList<>(5);
for (int i = 0;i<5;i++){
final Map map = new HashMap();
map.put("checkVal",params[i]);
// 创建线程,重写run方法
Thread iThread = new Thread(new Runnable() {
@Override
public void run() {
try {
// 调用存储过程
dao.executePKG(map);
}catch (Exception e){
//todo 打印错误日志
}
}
});
// 将线程放入集合
list.add(iThread);
// 异步启动线程
iThread.start();
}
for (Thread ithread : list) {
try {
// 等待所有的线程执行完毕
ithread.join();
} catch (InterruptedException e) {
// todo 打印错误日志
}
}
// 完成等待后,才能执行后续的逻辑代码
System.out.println("所有线程执行完毕,可以执行后续代码");
}
二、Callable类的使用(集合再次进行分割)
1.业务背景
批量下发优惠券,若客户已有抵扣券,则表示互斥,不能下发,调用已有接口检查互斥,接口每次只能接受1000个客户。
2.代码示例
主代码逻辑
package work;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class CallableStudy {
public static void main(String[] args) {
List<TUser> tUsers = new ArrayList<>();
// 对List集合切分多个List调用
List<List<TUser>> newList = getNewList(tUsers);
// 检查后,互斥客户的信息名单
List<Map<String,Object>> failList = new ArrayList<>();
// 检查后,不互斥客户的信息名单
List<Map<String,Object>> sucessList = new ArrayList<>();
// 虚拟奖品类型
String prizeType = "抵扣券";
// 异步线程处理结果
new CheckLoadDiscountThread(newList,failList,sucessList,prizeType).start();
}
private static List<List<TUser>> getNewList(List<TUser> tUsers) {
List<List<TUser>> allList = new ArrayList();
int arrSize = tUsers.size() % 1000 == 0 ? tUsers.size() /1000 : tUsers.size() /1000 + 1;
for(int i = 0;i < arrSize; i++){
// 外层代表要分割多少个集合
List<TUser> subList = new ArrayList();
for(int k = 1000 * i ;k < 1000 * (i+1);k++){
if (k < tUsers.size() ){
// 防止集合下标越界
subList.add(tUsers.get(k));
}
}
allList.add(subList);
}
return allList;
}
}
创建线程池,多线程批量数据
package work;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class CheckLoadDiscountThread extends Thread {
// 需要检查互斥客户的信息名单
private List<List<TUser>> tUsers;
// 检查后,互斥客户的信息名单
private List<Map<String,Object>> failList;
// 检查后,不互斥客户的信息名单
private List<Map<String,Object>> sucessList;
// 虚拟奖品类型
private String prizeType;
public CheckLoadDiscountThread(List<List<TUser>> tUsers,List<Map<String,Object>> failList,List<Map<String,Object>> sucessList,String prizeType){
this.tUsers = tUsers;
this.failList = failList;
this.sucessList = sucessList;
this.prizeType = prizeType;
}
@Override
public void run() {
// 重写的CheckLoadDiscountCallable返回Map结果,future类用Map接受
List<Future<Map>> futureList = new ArrayList<>();
// 线程数
int threadNumber = tUsers.size() > 50 ? 50 : tUsers.size();
ExecutorService fixedThredPool = Executors.newFixedThreadPool(threadNumber);
for (int k = 0;k<tUsers.size();k++){
futureList.add(fixedThredPool.submit(new CheckLoadDiscountCallable(tUsers.get(k),prizeType)));
}
for(int i = 0;i<futureList.size();i++){
Future<Map> objectFuture = futureList.get(i);
try {
while (true){
if(objectFuture.isDone() && !objectFuture.isCancelled()){
// 任务已经完成
// 获取执行结果
Map<String,Object> result = (Map<String, Object>) objectFuture.get();
// 互斥结果
List<Map<String,Object>> failList = (List<Map<String, Object>>) result.get("failList");
// 不互斥结果
List<Map<String,Object>> successList = (List<Map<String, Object>>) result.get("successList");
if(failList.isEmpty() && failList.size() > 0){
for (Map<String, Object> map : failList) {
failList.add(map);
}
}
if(successList.isEmpty() && successList.size() > 0){
for (Map<String, Object> map : successList) {
successList.add(map);
}
}
break;
} else {
// 任务没有完成处理,等待10毫秒
Thread.sleep(10);
}
}
} catch (Exception e){
// 打印日志
}
}
// 销毁线程池
fixedThredPool.shutdown();
}
}
业务要求检查是否互斥,重写callable方法,线程可返回结果
package work;
import com.example.demo.CacheDemo;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
public class CheckLoadDiscountCallable implements Callable<Map> {
private List<TUser> tUsers;
private String prizeType;
private OracleStudy service;
public CheckLoadDiscountCallable (List<TUser> tUser,String prizeType){
this.tUsers = tUsers;
this.prizeType = prizeType;
}
@Override
public Map<String,Object> call() throws Exception {
// 对要处理的集合进行业务处理
Map paraMap = new HashMap();
Map resultMap = new HashMap();
paraMap.put("userList",tUsers);
Map gbdCarInfo = service.getGBDCarInfo(paraMap);
if(gbdCarInfo == null){
resultMap.put("reason","调用系统失败");
resultMap.put("isMutex","T");
}else {
if("T".equals(gbdCarInfo.get("isSuccess"))){
List discountList = (List) gbdCarInfo.get("discountList");
if(discountList != null && discountList.size() > 0){
resultMap.put("reson","已有还款抵扣券");
resultMap.put("ixMutex","F");
} else {
//结果不互斥
resultMap.put("ixMutex","T");
}
} else {
resultMap.put("reson",gbdCarInfo.get("decrition"));
resultMap.put("ixMutex","T");
}
}
return resultMap;
}
}
三、线程不断轮询机制,自己构造生产者与消费者模型
1.业务背景
一个定时任务去处理每天十几万的数据,定时每次整点运行一次。优化其效率,任务触发生产者线程,判断十几万数据是否处理完成,并监控消费者线程中,线程队列数是否小于1000(1000举例)。当等待队列数小于1000并且还存在没有处理完成的数据,则补充消费者队列。若等待队列数大于1000并且还存在没有处理完成的数据,则主线程等待一段时间后,才继续看消费者队列数。
2.代码示例
package work;
import java.util.concurrent.*;
public class DesignThread {
private ThreadPoolExecutor consumerExecutor ;
private ExecutorService producerExecutor ;
/*线程是否在运行标识*/
private String flag = "no_live";
/*
* 创建生产者与消费者模式的线程
* corePoolSize 核心线程数 参考值:5
* maxmumPoolSize 最大线程数 参考值:5
* keepAliveTime 消费者失效时间 参考值5:l
* time 消费者失时间单位 参考者:分钟
* queueLength 消费者队列长度 参考值:5000*2 单个SQL取数长度*2
* producePoolNum 生产者线程个数 参考值:1
* */
private void initExecutor(int corePoolSize, int maxmumPoolSize, long keepAliveTime,
TimeUnit time,int queueLength,int producePoolNum){
//初始化消费者线程池
consumerExecutor = new ThreadPoolExecutor(corePoolSize,maxmumPoolSize,keepAliveTime,time,
new LinkedBlockingDeque<Runnable>(queueLength),new ThreadPoolExecutor.DiscardPolicy());
//初始化生产者线程
producerExecutor = Executors.newFixedThreadPool(producePoolNum);
//jvm关闭前销毁线程池
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
@Override
public void run() {
consumerExecutor.shutdown();
producerExecutor.shutdown();
}
}));
}
public void runProducerThread(final CommonDTO comDTO){
if("live".equals(flag)){
System.out.println("生产者正在运行中");
return;
}
//执行生产者线程
producerExecutor.execute(new Runnable() {
@Override
public void run() {
flag = "live";
while (true){
// 监控消费者队列,并补充队列数据
if (runTask(comDTO)){
break;
}
}
// 跳出循环表示数据已处理完成
flag = "no_live";
}
});
}
/*
* 消费者任务是否全部处理完成,是:true 不是:false
* */
private boolean runTask(CommonDTO comDTO) {
try {
int size = consumerExecutor.getQueue().size();
// 当消费者线程的等待队列小于单个SQL取值最大值时,补充队列数据进去
if(size < comDTO.getSQLMaxNum){
// 查询是否存在数据
String s = queryData();
if (s != null){
// 查询若存在数据,继续补充队列数据
insertQueueInfo(comDTO);
} else {
// 没有数据则表示全部处理完成
return true;
}
} else {
// 队列长度小于取数量,等待一下,让任务消费者线程继续执行任务
Thread.sleep(6000);
}
} catch (InterruptedException e) {
System.out.println("出现异常");
return true;
}
return false;
}
// 往队列补充数据
/**
*由于一般是分布式项目,必须加分布式锁进行控制
**/
private void insertQueueInfo(CommonDTO comDTO) {
//还是以批次号取值捞数进行处理
String batchNo = null;
// 获取一个分布式批次号
comDto.setBacthNo(getBatchNo());
batchNo = getBatchNo()
//1.获取锁
try{
if (lockService.lock(comDTO)){
//2.更新批次号,返回更新数量。仅需要保证更新批次号只有单台实例进行即可
Int updateNum = lockDao.updateBatchNo(batchNo);
}
} finally{
//3.解锁操作 自己的锁只能自己解锁
lockDao.unlock();
}
// 通过更新数量判断是否还有需要更新的数据
if (updateNum <= 0 ){
System.out.println("更新数量为0,已无需要处理的数据");
}
//4.通过批次号获取数据
List list = lockDao.upgetDataByBatchNo(batchNo);
// 判断取出的list是否还有数据
if (list == null ){
System.out.println("更新数量为0,已无需要处理的数据");
}
for (final DTO data : list){
consumerExecutor.execute(new Runnable() {
@Override
public void run(data) {
// 编写业务代码
}
});
}
}
private String queryData() {
return null;
}
}
/**
* 获取分布式锁的操作
*/
public Boolean lock(CommonDTO comDTO){
//参数:taskId:锁名称
//参数:aviableDate:失效时间,超过失效时间,锁应该失效 参考值:10分钟 aviableDate = sysdate + 10分钟
//参数:onewer:锁拥有者标识(可用批次号标识)
//getRetryNum 重试次数,参考值:20
for (int i= 0;i < comDTO.getRetryNum();i++){
if (lockDao.lock(taskId,aviableDate,onewer)){
return true;
}
//没抢到,线程休眠时间 参考值:500ms+RandomUtils.nextLong(0,500)
Thread.sleep(comDTO.getRetryTime());
}
return false;
}
///lockDao.lockSQL逻辑
lock(){
SQL:update t_info set value = '1',update = sysdate,aviable_date = #aviableDate#,onewer= #onewer#
where config_id = #taskId# and (
config_value = '0' or date_updated_date < sysdate -1 or aviable_date < sysdate
)
reture update > 0;
}
lockDao.unlock(
SQL:update t_info set value = '0',update = sysdate,aviable_date = #aviableDate#
where config_id = #taskId# and onewer = #onewer# and value = '1'
);
}
四、线程池参数的选择
核心线程数:2N(N:CPU核数)
最大线程数:2N+1(N:CPU核数)