多线程并发处理数据的问题

在现在的项目中遇到的一个问题。

我所做的短信平台,原来只是单线程发送短信的,但是由于公司的应用范围的扩大,短信的发送量成倍的增多,一批插入的短信量达到5W数据,如果按照以前的方式,发送过程十分缓慢。因为我们所用的第三方短信提供商只提供给我们10个并发的限制,所以我们采用10条线程进行读取。一次发10条,等10条发送完成以后再发送另外10条。

以下是程序:

 

private  void sendSmsLoop() throws Exception {
   
  
  for (int i=0; i< threads.length; i++){
   threads[i] = null;
  }  
  String sql =""
   +"select top 10 "
   +"smsSend.id "
   +",smsSend.phoneNumber "
   +",smsSend.content "
   +",smsSend.subcode  "
   +",smsSend.clientID  "
   +",smsSend.SID  "
   +"from  "
   +"smsSend "
   +"where "
   +"(smsSend.handleFlag = 0 "
   +"and datediff(minute, smsSend.registerDate, getDate()) < 30 "
   +"and left(smsSend.phoneNumber,3)  in ('130','131','132','155','156','186','134','135','136','137','138','139','150','151','152','154','157','158','159','187','188'))  or (ClientID='1' and sendDate is NULL)";
   
   //System.out.println(sql);
   Stmt = DBConn.createStatement();
   Rs = Stmt.executeQuery(sql);
   int inc = 0;
   while(Rs.next())
   {
    String ID = Rs.getString("id");
    String phoneNumber = Rs.getString("phoneNumber");//手机号
    String content = StrUtil.replaceIllegalString(Rs.getString("content")); //信息
    String subcode = Rs.getString("subcode");
    String clientID = Rs.getString("clientID");
    String sid = Rs.getString("SID");
   //在这里开始开启线程,因为我所选的是TOP10,所以一次可以启动10条线程
    SmsThread st = new SmsThread(ID,phoneNumber,content,subcode,clientID,sid);
    threads[inc] = st;
    threads[inc].start();
    inc++;
   }
   
   //循环判断10个线程的FLAG信息,判断FLAG必须都为TRUE了。才可以进行下一轮扫描。【判断为TRUE的结果就是只要该线程执行完毕后FLAG就赋值为TRUE】
   while(true)
   {
    boolean _flag = false;
    for (int i=0; i< threads.length; i++){
     if (threads[i] != null){
      SmsThread st = (SmsThread) threads[i];
      if(!st.flag)
      {
       _flag = true;
        break;
      }
     }
    }
    if (_flag){
     Thread.sleep(1000);
    }else{
     break;
    }
   }
   
   
 }
 class SmsThread extends Thread
 {
  public boolean flag = false;
  public String ID;
  public String phoneNumber;
  public String content;
  public String subcode;
  public String clientID;
  public String sid;
  public SmsThread()
  {
   
  }
  public SmsThread(String ID,String phoneNumber,String content,String subcode,String clientID,String sid)
  {
   this.ID = ID;
   this.phoneNumber =phoneNumber;
   this.content=content;
   this.subcode = subcode;
   this.clientID = clientID;
   this.sid = sid;
  }
  public void run()
  {
   try {
    sendSmsByStrategy(ID, phoneNumber, content, subcode, clientID,
      sid);
   } catch (Exception e) {
    e.printStackTrace();
   }

//每个线程执行完成以后都会将自己的FLAG设置为TRUE,所以我们在循环取数据的时候要保证所有的线程方法都执行了,才开始下一次的数据读取。
   flag = true;   
  }
 }
 private void sendSmsByStrategy(String id, String phoneNumber, String content,String SubCode,String clientID,String sid) throws Exception
 {
    SmsSendBatch send = new SmsSendBatch(DBConn);
    send.sendSms(id, phoneNumber, content, SubCode);
   
 }

 

 

 

----------------------------------------------------

对以上方法的改良版本V1.0。上面程序存在的一个性能问题就是,每次发完10条信息都会要重新去数据库取出一次数据,这样对性能也会造成一定的影响,现在对齐进行改良,因为我们根据每次并发8条线程对于发送短信是最快的【主要是短信提供商那边提供给我们虽然有10 个并发,但是我们自己测试8个并发是最好的,10个并发会有连接异常发生】。我改变读取策略,一次从数据库中读取80条信息,放在集合中,然后用8个线程分别每天处理10条信息进行发送,等发送完毕这80条信息,然后再去取另外的80条信息。为什么不一次性取多点数据呢,因为我们还有其他的数据任务在操作短信表,如果数据量一次取太大,会对数据库造成死锁,所以取的够用就可以了。

具体代码实现:

private  void sendSmsLoop() throws Exception {
   
  
  threads.clear(); 
  smslist.clear();
  String sql =""
   +"select top 80 "
   +"smsSend.id "
   +",smsSend.phoneNumber "
   +",smsSend.content "
   +",smsSend.subcode  "
   +",smsSend.clientID  "
   +",smsSend.SID  "
   +"from  "
   +"smsSend_temp_tuping as smsSend "
   +"where "
   +"(smsSend.handleFlag = 0 "
   //+"and datediff(minute, smsSend.registerDate, getDate()) < 30 "
   //+"and left(smsSend.phoneNumber,3)  in ('130','131','132','155','156','186','134','135','136','137','138','139','150','151','152','154','157','158','159','187','188'))  or (ClientID='1' and sendDate is NULL) order by Priority desc,registerdate asc";
   +"and left(smsSend.phoneNumber,3)  in ('130','131','132','155','156','186','134','135','136','137','138','139','150','151','152','154','157','158','159','187','188'))  or (ClientID='1' and sendDate is NULL) order by registerdate asc";
  
     // System.out.println(sql);
   Stmt = DBConn.createStatement();
   Rs = Stmt.executeQuery(sql);
   int inc = 0;
   while(Rs.next())
   {
    String ID = Rs.getString("id");
    String phoneNumber = Rs.getString("phoneNumber");//手机号
    String content = StrUtil.replaceIllegalString(Rs.getString("content")); //信息
    String subcode = Rs.getString("subcode");
    String clientID = Rs.getString("clientID");
    String sid = Rs.getString("SID");
    
    SmsBean sb = new SmsBean();
    sb.setID(ID);
    sb.setPhoneNumber(phoneNumber);
    sb.setContent(content);
    sb.setSubcode(subcode);
    sb.setClientID(clientID);
    sb.setSid(sid);
    smslist.add(sb);
    inc++;
   }
   
   
   for(int i=0;i<smslist.size();i++)
   {
    SmsBean sb = (SmsBean)smslist.get(i);
    SmsThread ut = new SmsSendClientBatchNew().new SmsThread(sb.getID(),sb.getPhoneNumber(),sb.getContent(),sb.getSubcode(),sb.getClientID(),sb.getSid());
    threads.add(ut);
    ut.start();

 //判断每开启8个线程就开始等待,等待到该8个线程执行完毕,然后将该8个线程清空,然后再开启后面的8个。

   if(inc>=10)
   {
    System.out.println("信息发送量估计会大于10条信息,接着发!");
   }
   else
   {
    System.out.println("信息发送量小于10条信息,休息5秒!");
    Thread.sleep(5000);
   }
 }

 

该方法比第一种方法的效率要高点,同时也减少了对数据库的操作。

------------------------------------------------------

对以上方法的改良,上一种方式也存在缺点,比如如果有24条数据,第一条线程处理8条,第2条处理8条,第三条处理6条,如果第一条线程提比后面两条处理的都快很多,那么先处理完成的线程将被浪费,我们可以采用线程池的概念。

与数据库连接池类似的是,线程池会启动大量的空闲线程,程序将一个RUNNABLE对象的线程给线程池,线程池就会启动一挑线程来执行该对象的RUN方法,当RUN方法执行结束后,该线程不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个RUNABLE对象的RUN方法。

JDK1.5提供了一个EXECUTORS工厂类来产生线程池,该工厂类包含如下几个静态工厂方法来创建线程池:

1、newCachedThreadPool(): 创建一个具有缓存功能的线程池,系统根据需要创建线程,这些线程将会被缓存在线程池中。

2.newFixedThreadPool(int n):创建一个可重用的具有固定线程数的线程池。

3.newSingleThreadExecutor():创建一个只有单线程的线程池,他相当于newFixedThreadPool方法时传入参数为1

4.newScheduledThreadPool(int corePoolSize):创建具有指定线程数的线程池,他可以再制定延迟后执行线程任务,corePoolSize指池中所保存的线程数,即使线程是空闲的也被保存在线程池内。

5.newSingleThreadScheduledExecutor()创建一个只有一条线程的线程池,它可以再制定延迟后执行线程任务。

 

以上5个方法前三个返回一个 ExecutorService对象,该对象代表一个线程池,他可以执行RUNNABLE对象或CALLABLE对象所代表的线程。

EXECUTORSERVICE代表尽快执行线程的线程池(只要线程池中有空闲线程立即执行线程任务),程序只要将一个RUNNABLE或CALLABLE对象提交给线程池即可,该线程池就会尽快执行该任务。他提供了三个方法:

1. Future<?> submit(Runnable task):将一个RUNNABLE对象提交给指定的线程池,线程池将在有空闲线程时执行对象所代表的任务,其中FUTURE对象代表RUNNABLE人物的返回值,但RUN方法没有返回值,所以FUTURE对象将在RUN方法执行结束后返回NULL,但可以调用FUTURE的isDone(),isCancelled()方法获得 runnable对象的执行状态。

2.Future<T> submit(Runnable task,T result):将一个RUNNABLE对象提交给指定的线程池,线程池将在空闲线程时候执行对象的任务,RESULT显示指定线程执行结束后的返回值,所以FUTURE对象将在RUN方法执行结束后返回RESULT。

3.Future<T> submit(Callable<T> task):将一个Callable对象提交给指定的线程池,线程池将在空闲线程时候执行对象的任务,FUTURE代表将在RUN方法执行结束后返回值。

 

代码:

首先开启具有8个固定线程的线程池。

public ExecutorService pool = Executors.newFixedThreadPool(8);

 

private  void sendSmsLoop() throws Exception {
   
  
  threads.clear(); 
  smslist.clear();
  String sql =""
   +"select top 80 "
   +"smsSend.id "
   +",smsSend.phoneNumber "
   +",smsSend.content "
   +",smsSend.subcode  "
   +",smsSend.clientID  "
   +",smsSend.SID  "
   +"from  "
   +"smsSend_temp_tuping as smsSend "
   +"where "
   +"(smsSend.handleFlag = 0 "
   //+"and datediff(minute, smsSend.registerDate, getDate()) < 30 "
   +"and left(smsSend.phoneNumber,3)  in ('130','131','132','155','156','186','134','135','136','137','138','139','150','151','152','154','157','158','159','187','188'))  or (ClientID='1' and sendDate is NULL) order by registerdate asc";
   
     // System.out.println(sql);
   Stmt = DBConn.createStatement();
   Rs = Stmt.executeQuery(sql);
   int inc = 0;
   while(Rs.next())
   {
    String ID = Rs.getString("id");
    String phoneNumber = Rs.getString("phoneNumber");//手机号
    String content = StrUtil.replaceIllegalString(Rs.getString("content")); //信息
    String subcode = Rs.getString("subcode");
    String clientID = Rs.getString("clientID");
    String sid = Rs.getString("SID");
    
    SmsBean sb = new SmsBean();
    sb.setID(ID);
    sb.setPhoneNumber(phoneNumber);
    sb.setContent(content);
    sb.setSubcode(subcode);
    sb.setClientID(clientID);
    sb.setSid(sid);
    smslist.add(sb);
    inc++;
   }
   

   for(int i=0;i<smslist.size();i++)
   {
    SmsBean sb = (SmsBean)smslist.get(i);
    SmsThread ut = new SmsSendClientBatchPool().new SmsThread(sb.getID(),sb.getPhoneNumber(),sb.getContent(),sb.getSubcode(),sb.getClientID(),sb.getSid());

//开启一个线程,并将该线程放入到线程池中,然后将该线程的返回值放入到一个List<Future> 的结合中,用来保存所开启线程的返回结果。

   
   
   if(inc>=10)
   {
    System.out.println("信息发送量估计会大于10条信息,接着发!");
   }
   else
   {
    System.out.println("信息发送量小于10条信息,休息5秒!");
    Thread.sleep(5000);
   }
   
   
 }

 

用了线程池以后,如果一次取80条数据,原来每次要用8个线程来发,但是线程池每次也许只用5-6个就可以用来发送8条信息了。因为有可能第一个用完的线程在发送第7条信息的时候又被拿出来用。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值