【Java多线程】模拟短信发送并统计成功与失败条数
一、引言
笔者刚出校面试那会,对多线程的知识并不熟悉,在被问到了一个多线程场景题时直接蒙圈了,最近后知后觉想到了这个题,于是去了解了一下Java多线程的知识,并且使用Java的多线程模拟解出这道题(多线程只是解决这个场景的方案之一,如有其他更好的方案,还请多多指教)。
二、前置知识
需要了解java.util.concurrent包下的CountDownLatch类的用法。这里推荐文章CountDownLatch介绍和使用。
三、代码
1.发送短信:这里只做简单的业务场景模拟,真实发送短信业务还需自行修改
1 public boolean sendMsg() throws InterruptedException { 2 // 模拟短信发送,95%的成功率 3 boolean isSuccess = ThreadLocalRandom.current().nextDouble(1) < 0.95; 4 // 模拟短信延迟,发送失败时延迟时间更长 5 Thread.sleep(isSuccess ? 100 : 500); 6 if (isSuccess) { 7 return true; 8 }
9 return false; 10 }
说明:上述代码使用了jdk自带的ThreadLocalRandom类来生成0-1的伪随机数,模拟发送短信业务平均有95%的发送成功率。
2.统计成功/失败条数(核心代码):
初始化一个简单线程池:
1 ExecutorService threadPool = Executors.newFixedThreadPool(1000);
统计执行条数:
1 CountDownLatch latch = new CountDownLatch(sendCount); 2 // 成功/失败统计变量 3 AtomicInteger successCnt = new AtomicInteger(); 4 AtomicInteger failCnt = new AtomicInteger(); 5 for (int i = 0; i < 1000; i++) { 6 threadPool.submit(() -> { 7 try { 8 if (this.sendMsg()) { 9 successCnt.incrementAndGet(); 10 log.info("发送成功"); 11 } else { 12 failCnt.incrementAndGet(); 13 log.info("发送短信失败了..."); 14 } 15 latch.countDown(); // 计数器 - 1 16 } catch (InterruptedException e) { 17 log.info("业务异常..."); 18 } 19 }); 20 } 21 22 try { 23 latch.await(); 24 } catch (InterruptedException e) { 25 log.info("发生阻塞了..."); 26 e.printStackTrace(); 27 } finally { 28 threadPool.shutdown(); 29 } 30 log.info("发送成功条数: " + successCnt.get() + ", 发送失败条数: " + failCnt.get());
四、执行结果
1 ... 2 信息: 发送短信成功了... 3 信息: 发送短信成功了... 4 信息: 发送短信失败了... 5 ... 6 信息: 发送成功条数: 964, 发送失败条数: 36
从控制台的输出可以看到能够正确统计到短信发送成功/失败条数的结果。
五、思考
如果要模拟在发送短信时,因线程中断或其他原因致使线程挂掉从而引发 CountDownLatch 计数器无法自减,这将使得 CountDownLatch 的 await() 迟迟得不到资源释放,进而使得主线程陷入死锁状态。这应该如何解决呢?
接下来模拟线程中断的场景。
1. 在 sendMsg() 方法中的短信发送失败时的逻辑添加线程中断代码 Thread.currentThread().interrupt(); 完整代码如下:
1 public boolean sendMsg() throws InterruptedException { 2 // 模拟短信发送,95%的成功率 3 boolean isSuccess = ThreadLocalRandom.current().nextDouble(1) < 0.95; 4 // 模拟短信延迟,发送失败时延迟时间更长 5 Thread.sleep(isSuccess ? 100 : 500); 6 if (isSuccess) { 7 return true; 8 } 9 Thread.currentThread().interrupt(); 10 return false; 11 }
2. 在 for 循环发送短信代码中修改逻辑,只有发送成功的短信才会将计数器自减,代码如下:
1 for (int i = 0; i < 1000; i++) { 2 threadPool.submit(() -> { 3 try { 4 if (this.sendMsg()) { 5 successCnt.incrementAndGet(); 6 log.info("发送成功"); 7 latch.countDown(); // 计数器 - 1 8 } else { 9 failCnt.incrementAndGet(); 10 log.info("发送短信失败了..."); 11 } 12 } catch (InterruptedException e) { 13 log.info("业务异常..."); 14 } 15 }); 16 }
3. 运行结果
1 ... 2 信息: 发送短信成功了... 3 信息: 发送短信成功了... 4 信息: 发送短信失败了... 5 信息: 发送短信成功了...
可以看出:并不会输出"发送成功条数/发送失败条数",而是一直卡在控制台中
4. 异常兜底方案
相信如果读了上文推荐的CountDownLatch介绍和使用,小伙伴们也知道了解决方法,也就是使用带有参数的 await() 方法,官方的解释如下:如果超过了指定的等待时间,将会完全不等待该线程。
也就是说如果某一个线程中断了,只要超过了我们设置的响应时间,主线程也不会因为其他线程的中断导致计数器无法被减到0而引发的线程阻塞。

六、完整代码
最后附上完整代码。
1 /** 2 * 多线程发送短信消息,统计多少条发送成功,多少条发送失败 3 */ 4 public class MultiThread2SendMsg { 5 6 // 日志 7 private final Logger log = Logger.getLogger(MultiThread2SendMsg.class.getName()); 8 // 线程池 9 private ExecutorService threadPool; 10 11 /** 12 * 模拟短信发送 13 * @return true:发送成功,false:发送失败 14 * @throws InterruptedException 中断异常 15 */ 16 public boolean sendMsg() throws InterruptedException { 17 // 模拟短信发送,95%的成功率 18 boolean isSuccess = ThreadLocalRandom.current().nextDouble(1) < 0.95; 19 // 模拟短信延迟,发送失败时延迟时间更长 20 Thread.sleep(isSuccess ? 100 : 500); 21 if (isSuccess) { 22 return true; 23 } 24 Thread.currentThread().interrupt(); 25 return false; 26 } 27 28 public void calcSendMsgCount(int sendCount){ 29 // 初始化线程数量 30 threadPool = Executors.newFixedThreadPool(sendCount); 31 CountDownLatch latch = new CountDownLatch(sendCount); 32 // 成功/失败统计 33 AtomicInteger successCnt = new AtomicInteger(); 34 AtomicInteger failCnt = new AtomicInteger(); 35 for (int i = 0; i < sendCount; i++) { 36 threadPool.submit(() -> { 37 try { 38 if (this.sendMsg()) { 39 successCnt.incrementAndGet(); 40 log.info("发送成功"); 41 latch.countDown(); // 计数器 - 1 42 } else { 43 failCnt.incrementAndGet(); 44 log.info("发送短信失败了..."); 45 } 46 } catch (InterruptedException e) { 47 log.info("业务异常..."); 48 } 49 }); 50 } 51 52 try { 53 latch.await(10, TimeUnit.SECONDS); 54 } catch (InterruptedException e) { 55 log.info("发生阻塞了..."); 56 e.printStackTrace(); 57 } finally { 58 threadPool.shutdown(); 59 } 60 log.info("发送成功条数: " + successCnt.get() + ", 发送失败条数: " + failCnt.get()); 61 } 62 63 public static void main(String[] args) { 64 new MultiThread2SendMsg().calcSendMsgCount(1000); 65 } 66 }

 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号