项目中遇到了一个关于多线程使用的问题,由于对这一块不熟,所以下个笔记记录一些。
需求大致如下:前端发送一个批量注册人员请求,java后台封装数据然后发送http请求到服务器进行注册。每一个请求最多处理10个人员,如果有批量注册一万个人,就要发送1000个请求。
处理方法:java后台固定跑5个线程(太多了处理服务器处理不过来)来处理这个功能,处理线程会一直去请求队列中查看是否有请求,如果有新请求就会唤醒处理线程处理,将处理结果存到结果队列,主线程等到所有请求都处理完了,再处理结果队列。
在这个例子中,我重写了一个demo来处理1~1,000,000,000累加,处理方法与上面的类似
处理该功能的主线程:
public class ImportThread extends Thread {
//结果队列 Triplet:List<Integer>每线程执行结果,String进度json,Date更新时间用于定时
public static Map<String,Triplet> mapList = new HashMap<>();
public static List<Triplet> lstReq = new ArrayList<>(); //请求队列
private Long total; //累加总数
private Long per; //每个线程处理的数量
private String uuid; //uuid代表某一次请求 请求队列、结果队列、进度队列都要用到
ImportThread(Long total,Long per,String uuid){
this.total = total;
this.per = per;
this.uuid = uuid;
}
public void run(){
try{
mapList.put(uuid, new Triplet(new ArrayList<String>(),"",new Date()));
long size = total/per;
for(int i=0;i<size;i++){
Triplet triplet = new Triplet<>(uuid,i*per,(i+1)*per);
synchronized (lstReq){
lstReq.add(triplet); //将请求插入到请求队列中
lstReq.notifyAll(); //唤醒线程
}
}
//处理返回结果
List<Long> result_ = null;
while(true){
//更新结果队列直到请求完成
Triplet resTriplet = mapList.get(uuid);
result_ = (List<Long>) resTriplet.getValue0();
JSONObject socketObject = new JSONObject();
float progress = result_.size()/(float)size*100;
socketObject.put("progress", String.format("%1.2f", progress));
socketObject.put("msg", result_.size()==size?"完成":"处理中");
Triplet newResult = new Triplet(result_,socketObject.toString(),new Date());
mapList.put(uuid, newResult);
if(result_.size()!=size){
Thread.sleep(1000);
}else{break;}
}
long sum=0;
for(long num:result_){
sum+=num;
}
System.out.println("结果:"+sum);
}catch(Exception e){
e.printStackTrace();
}
}
}
处理线程:
public class BatchThread extends Thread {
public void run(){
Triplet triplet = null;
while(true){
try{
//每十秒查看请求队列是否有请求
synchronized(ImportThread.lstReq) {
if(ImportThread.lstReq.isEmpty()) {
ImportThread.lstReq.wait(10000L);
}
if(!ImportThread.lstReq.isEmpty()) {
triplet = ImportThread.lstReq.remove(0);
System.out.println(String.format("请求队列还剩:%s",ImportThread.lstReq.size()));
}
}
if(triplet == null)continue;
//业务代码 累加
long start = (long) triplet.getValue1(); //开始值
long end = (long) triplet.getValue2(); //结束值
long sum = 0;
for(long i=start;i<end;i++){
sum+=i;
}
Triplet tripletRes = ImportThread.mapList.get(triplet.getValue0());
List<Long> result_ = (List<Long>) tripletRes.getValue0();
result_.add(sum);
Triplet triplet1 = new Triplet(result_,tripletRes.getValue1(),new Date());
//将结果放入结果队列中
ImportThread.mapList.put((String) triplet.getValue0(), triplet1);
Thread.sleep(500);//为了方便看结果,等待0.5s
} catch (Exception e){
e.printStackTrace();
}
triplet = null;
}
}
}
获取进度线程(进度是通过前端定时调用接口返回,这里为了方便用线程做个模拟)
public class ProgressThread extends Thread {
private String uuid;
ProgressThread(String uuid){
this.uuid = uuid;
}
public void run(){
try{
while(true){
Triplet triplet = ImportThread.mapList.get(uuid);
if(triplet==null){
Thread.sleep(100);
continue;
}
String progress = (String) triplet.getValue1();
System.out.println(progress);
JSONObject obj = new JSONObject(progress);
String prg = obj.getString("progress");
if(prg.equals("100.00")){
break;
}else{
Thread.sleep(1000);
}
}
}catch(Exception e){
e.printStackTrace();
}
}
}
测试:
public class App
{
public static void main( String[] args )
{
//整个系统固定分配3个线程跑这功能
for(int i=0;i<3;i++){
new BatchThread().start();
}
new ImportThread(1000000000L,100000000L,"uuid").start();//启动执行线程
//进度是通过前端定时调用接口返回,这里为了方便做个模拟
new ProgressThread("uuid").start();;//启动获取进度线程
}
}