线程池
1. 概述
- 线程池做的工作主要是:控制运行的线程数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量,超出数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来执行。
- 线程池的主要特点:线程复用,控制最大并发数,管理线程。
- 线程池的优势:
- 降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 提高响应速度:当任务到达时,任务可以不需要等待线程创建就能立即执行。
- 提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性。使用线程池可以进行统一分配,调优和监控。
2. 三个方法
-
ExecutorService threadPool = Executors.
newSingleThreadExecutor()
:创建单个线程-
案例:
public class ThreadPoolTest1 { public static void main(String[] args) { //创建单个线程的线程池 ExecutorService pool = Executors.newSingleThreadExecutor(); try { for (int i = 0; i < 20; i++) { pool.execute(()->{ System.out.println(Thread.currentThread().getName() + " is OK!"); }); } } catch (Exception e) { e.printStackTrace(); } finally { //关闭线程池 pool.shutdown(); } } }
结果:最多只有一个线程
-
- ExecutorService threadPool = Executors.
newFixedThreadPool(i)
:创建一个有i个线程的线程池(固定大小线程池)
-
案例
public class ThreadPoolTest2 { public static void main(String[] args) { //创建5个线程的线程池 ExecutorService pool = Executors.newFixedThreadPool(5); try { for (int i = 0; i < 20; i++) { pool.execute(()->{ System.out.println(Thread.currentThread().getName() + " is OK!"); }); } } catch (Exception e) { e.printStackTrace(); } finally { //关闭线程池 pool.shutdown(); } } }
结果:最多可以有五个线程
-
ExecutorService threadPool = Executors.
newCachedThreadPool()
:可伸缩线程池,线程池根据需要可以扩容,遇强则强遇弱则弱-
案例
public class ThreadPoolTest3 { public static void main(String[] args) { //创建单个线程的线程池 ExecutorService pool = Executors.newCachedThreadPool(); try { for (int i = 0; i < 20; i++) { pool.execute(()->{ System.out.println(Thread.currentThread().getName() + " is OK!"); }); } } catch (Exception e) { e.printStackTrace(); } finally { //关闭线程池 pool.shutdown(); } } }
-
-
上述的三个创建线程池的方法有弊端,阿里巴巴java开发文档中也提出这样的约束:
9.3 七个参数
- 上述的三个方法的源码中,我们不难发现,虽然我们没有传入参数或者只传入了一个参数,但是都会返回一个有七个参数的线程池。
- 七个参数:
int corePoolSize
:核心线程数。int maximumPoolSize
:最大线程数。表明线程中最多能够创建的线程数量,此值必须大于等于1。long keepAliveTime
:空闲的线程保留的时间,超时了还没有调用就会释放。TimeUnit unit
:空闲线程的保留时间单位。BlockingQueue<Runnable> workQueue
:阻塞队列,存储等待执行的任务。ThreadFactory threadFactory
:线程工厂:创建线程的,一般用默认值,不需要改动。RejectedExecutionHandler handler
:拒绝策略。
9.4 四个拒绝策略
- 四种拒绝策略在ThreadPoolExecutor是四个内部类。
-
当线程池中的线程不够用时,再去访问
-
AbortPolicy abortPolicy = new ThreadPoolExecutor.
AbortPolicy()
;默认的-
该策略下,当任务添加到线程池中被拒绝时,它将直接丢弃任务,并抛出RejectedExecutionException异常
-
案例
public class PolicyTest1 { public static void main(String[] args) { ThreadPoolExecutor pool = new ThreadPoolExecutor( 2,//核心线程数 5,//最大线程数 3,//空闲存活时间 TimeUnit.SECONDS,//时间单位 new LinkedBlockingQueue<>(3),//阻塞队列 Executors.defaultThreadFactory(),//线程工厂 new ThreadPoolExecutor.AbortPolicy());//AbortPolicy拒绝策略 //模拟大量并发的线程 while (true){ pool.execute(()->{ System.out.println("queue="+pool.getQueue().size()); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } }); } } }
-
结果
-
-
DiscardPolicy discardPolicy = new ThreadPoolExecutor.
DiscardPolicy()
;- 该策略下,当任务添加到线程池中被拒绝时,默认情况下它将直接丢弃任务,但不会抛出异常。
-
DiscardOldestPolicy discardOldestPolicy = new ThreadPoolExecutor.
DiscardOldestPolicy()
;- 该策略下,当任务添加到线程池中被拒绝时,线程池会放弃等待队列中最旧的未处理任务(即最早进入队列的任务),然后尝试将被拒绝的任务添加到等待队列中。
-
CallerRunsPolicy callerRunsPolicy = new ThreadPoolExecutor.
CallerRunsPolicy()
;-
该策略下,当线程池不够用时,任务将不进入线程池执行,由调用者线程去执行。如果执行程序已关闭,则会丢弃该任务。
-
案例
public class PolicyTest4 { public static void main(String[] args) { ThreadPoolExecutor pool = new ThreadPoolExecutor( 2, 5, 3, TimeUnit.SECONDS, new LinkedBlockingQueue<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());//CallerRunsPolicy()拒绝策略 //模拟大量并发的线程 while (true){ pool.execute(()->{ System.out.println(Thread.currentThread().getName() + "->queue:" + pool.getQueue().size()); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } }); } } }
-
结果
-
9.5 自定义合适的线程池
CPU密集型
:电脑的核数是几核就将线程池的最大线程数设为即;选择maximunPoolSize的大小- Runtime.getRuntime().availableProcessors():获取cpu核数
I/O密集型
:I/O十分占用资源;I/O密集型就是判断我们程序中十分耗I/O的线程数量,大约是最大I/O数的一倍到两倍之间