【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 }

 

posted @ 2024-06-03 23:23  哒啦蹦哒啦  阅读(159)  评论(0)    收藏  举报