0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 385 386 387 388 389 390 391 392 393 394 395  「视频版」当线程池溢出之后,程序会奔溃吗?面试突击 007 期
英语文化交流 > 技术博客 > 「视频版」当线程池溢出之后,程序会奔溃吗?面试突击 007 期
「视频版」当线程池溢出之后,程序会奔溃吗?面试突击 007 期
时间: 分类:技术博客

本文的面试题是,当线程池的任务溢出之后,程序会奔溃吗?
这个问题问的是关于线程池的任务数超过线程池的承载能力之后,会出现什么情况?
那么,我们本文就手撸模拟一个线程池溢出的情况,来看程序的执行情况。
涉及知识点

  1. 核心线程数和最大线程数有什么区别?
  2. 如何模拟线程池溢出?
  3. 拒绝策略的执行流程是什么?
  4. 什么是线程池的拒绝策略?
  5. Java 自带的拒绝策略有哪些?
  6. 如何自定义拒绝策略?
  7. 视频面试答案

视频内容(因为视频比较大,分成了两个视频来展示 ):

图文面试答案

当线程池的任务溢出之后,程序并不会奔溃,这时候会触发线程池的拒绝策略,Java 自带的拒绝策略有四种:
AbortPolicy:终止策略,线程池终止执行,并直接抛出异常,Java 默认此拒绝策略;
CallerRunsPolicy:把任务交给当前线程执行(本来是线程池自己要执行的,结果处理不过来就交给当前的主线程去处理);
DiscardPolicy:忽略此任务(最新的任务);
DiscardOldestPolicy:忽略最早的任务(最久的任务)。
拒绝策略的执行流程比较绕,这是因为线程池有三个重要的参数:核心线程数(corePoolSize)、最大线程数(maximumPoolSize)、线程池的任务队列(BlockingQueue),大部分搞不清楚核心线程数和最大线程数有什么区别?
核心线程数是指在正常情况下线程池内的线程数量;而最大线程数指的是当线程池的任务队列存储超过最大值之后,可以创建最多的线程数量。
当任务比较少的时候,线程数量会根据设置的超时时间,回归线程的数量为核心线程数量,这个时候最大线程数就暂时没用了(没有发挥的余地了)。
拒绝策略的执行流程是:当提交的任务数量大于核心线程数时,任务会被放入到线程池的任务队列中,当任务超过了最大队列值时,判断当前线程数量是否大于最大线程数,如果小于最大线程数则会新创建线程处理次任务,相反的情况下就会执行拒绝策略,如下图所示:
「视频版」当线程池溢出之后,程序会奔溃吗?面试突击 007 期

模拟线程池溢出

 public static void main(String[] args) {
     ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 4, 10, TimeUnit.SECONDS,
           new LinkedBlockingQueue<>(1),
           new ThreadPoolExecutor.AbortPolicy());
     for (int i = 0; i < 6; i++) {
         executor.execute(() -> {
             System.out.println(Thread.currentThread().getName());
         });
     }
 }
程序的执行结果如下:
pool-1-thread-2
pool-1-thread-2
pool-1-thread-1
pool-1-thread-3
pool-1-thread-4
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task org.example.App$$Lambda$1/1096979270@7cca494b rejected from java.util.concurrent.ThreadPoolExecutor@7ba4f24f[Running, pool size = 4, active threads = 4, queued tasks = 1, completed tasks = 0]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
at org.example.App.main(App.java:13)

从执行结果可以看出,循环在执行第 6 次就抛出异常了,这是因为最大线程数为 4,而队列最大只能存储 1 个任务,所以在第 6 个任务过来的时候,线程池已经超负荷运行了,只能执行拒绝策略了,而我们设置的拒绝策略是 AbortPolicy 所以会抛出异常。
自定义拒绝策略

除了 Java 自带的四种拒绝策略外,我们还可以自定义拒绝策略,代码如下:

public static void main(String[] args) throws InterruptedException {
    ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 4, 10, TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(1), new RejectedExecutionHandler() {
        @Override
        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
            // 添加业务处理代码
            System.out.println("自定义拒绝策略");
        }
    });
    for (int i = 0; i < 7; i++) {
        executor.execute(() -> {
            System.out.println(Thread.currentThread().getName());
        });
    }
}

以上程序执行结果如下:
自定义拒绝策略
自定义拒绝策略
pool-1-thread-1
pool-1-thread-1
pool-1-thread-2
pool-1-thread-3
pool-1-thread-4
可以看出自定义拒绝策略,只需要重写 RejectedExecutionHandler 接口的 rejectedExecution 方法即可,可以在此方法中添加自己的业务处理代码。
小结

本文讲了线程池任务新增时的执行流程,先判断是否有空闲线程,如果的话直接执行任务,如果没有的话再判断任务队列是否是否饱和,如果不饱和把任务存储到队列中,如果饱和需要判断当前线程数是否大于最大线程数,如果小于则新增线程执行此任务,反之则执行拒绝策略。Java 提供了四种拒绝策略,你可以通过重写 RejectedExecutionHandler 接口来自定义拒绝策略。
更多执行细节和更多知识点说明,详见本文的视频部分。
【END】
近期热文

  • HashMap 为什么会导致 CPU 100%?面试突击 006 期
  • 面试突击 005 | Redis 是如何实现高可用的?它的实现方式有哪些?
  • 面试突击 004 | 如何排查 Redis 中的慢查询?视频实战篇
  • 面试突击 003 | Redis 如何实现查询附近的人?
  • 面试突击 002 | Redis 是如何处理已过期元素的?
  • 面试突击 001 | Redis 如何从海量数据中查询出某一个 Key?
  • Java面试详解(2020版):500+ 面试题和核心知识点详解
    关注下方二维码,订阅更多精彩内容
    「视频版」当线程池溢出之后,程序会奔溃吗?面试突击 007 期
随机阅读

Copyright © 2017 英语文化交流 All Rights Reserved.