在项目中学习多线程

一、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
		forint 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核数)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值