JUC线程池

目录

1、线程池是什么?

2、为什么要使用线程池?

3、JUC中线程池的三大线程池

3.1获取单一线程的线程池 newSingleThreadExecutor()

3.2获取固定数量线程的线程池 newFixedThreadPool(int nThreads)

3.3获取线程数量可伸缩的线程池 Executors.newCachedThreadPool()

4、获取线程池的7大参数

5、自定义一个线程池

6、四种拒绝策略

6.1AbortPolicy()

6.2DiscardPolicy()

6.3CallerRunsPolicy()

6.4DiscardOldestPolicy()


1、线程池是什么?

线程池就是创建若干个可执行的线程放入一个池(容器)中,有任务需要处理时,会提交到线程池中的任务队列,处理完之后线程并不会被销毁,而是仍然在线程池中等待下一个任务。

2、为什么要使用线程池?

创建一个线程需要调用操作系统内核的 API,操作系统要为线程分配资源,CPU消耗成本很高,所以线程是一个重量级的对象,应该避免频繁创建和销毁。
使用线程池就能很好地避免频繁创建和销毁。

基本的思想就是:之前的操作是需要线程去执行任务的时候去新建一个线程,执行完任务后,再销毁线程,十分的浪费资源。现在是在使用线程之前维护好一个有一个或多个线程的池子,在需要执行任务的时候就取一个线程去执行,执行完之后,再归还给线程池,这样可以实现资源的再利用。

作用

  1. 降低资源的消耗
  2. 提高响应速度
  3. 便于管理线程
  4. 线程复用,可以控制最大并发数

3、JUC中线程池的三大线程池

3.1获取单一线程的线程池 newSingleThreadExecutor()

newSingleThreadExecutor();顾名思义,这个线程池中只有一个线程去执行任务

//1、获取单一线程池,即线程池中只有一个线程在工作
ExecutorService executorService = Executors.newSingleThreadExecutor();
 public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

 线程池对象有一个execute()方法,其实可以理解为往线程池中提交任务,参数是一个Runnable

 void execute(Runnable command);

 执行以下代码:

public class PoolTest {
    public static void main(String[] args) {

        //1、获取单一线程池,即线程池中只有一个线程在工作
        ExecutorService executorService = Executors.newSingleThreadExecutor();
       try {
            for (int i = 1; i <=6 ; i++) {
                executorService.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + "执行了");
                });
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            executorService.shutdown();//使用完一定要释放线程池资源
        }

    }
}

 结果:

pool-1-thread-1执行了
pool-1-thread-1执行了
pool-1-thread-1执行了
pool-1-thread-1执行了
pool-1-thread-1执行了
pool-1-thread-1执行了

由此可见,执行不同任务的其实都是一个线程

3.2获取固定数量线程的线程池 newFixedThreadPool(int nThreads)

//nThreads代表线程的数量
newFixedThreadPool(int nThreads)
 public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
//2、获取固定线程数的线程池
      ExecutorService executorService = Executors.newFixedThreadPool(5);
            try {
                for (int i = 1; i <=5 ; i++) {
                    executorService.execute(() -> {
                        System.out.println(Thread.currentThread().getName() + "执行了");
                    });
                }
            }catch (Exception e){
                e.printStackTrace();
            }finally {
            executorService.shutdown();//使用完一定要释放线程池资源
        }

执行结果:可见有五个线程执行任务,为我们指定的固定长度

pool-1-thread-1执行了
pool-1-thread-2执行了
pool-1-thread-3执行了
pool-1-thread-4执行了
pool-1-thread-5执行了

3.3获取线程数量可伸缩的线程池 Executors.newCachedThreadPool()

 //3、线程数量可伸缩的线程池
 ExecutorService executorService = Executors.newCachedThreadPool();

    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

执行测试一

ExecutorService executorService = Executors.newCachedThreadPool();
            try {
                for (int i = 1; i <=5 ; i++) {
                    executorService.execute(() -> {
                        System.out.println(Thread.currentThread().getName() + "执行了");
                    });
                }
            }catch (Exception e){
                e.printStackTrace();
            }finally {
            executorService.shutdown();//使用完一定要释放线程池资源
        }

 测试一结果:

pool-1-thread-1执行了
pool-1-thread-2执行了
pool-1-thread-4执行了
pool-1-thread-3执行了
pool-1-thread-5执行了

然后我们发现当有5个任务的时候,线程池中有5个线程来执行它们

然后我们将任务数改为100的时候,发现有40个线程执行了任务。newCachedThreadPool()获取的是一个线程数不固定的线程池,后面我们将原理。

4、获取线程池的7大参数

前面我列出了获取每种线程池的方法的源码,最终我们都发现它们都new了同一个类(ThreadPoolExecutor)的对象

new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());

 查看它的源码:

 public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
       ..............
    }

我们从源码中发现构建一个新的线程池对象有七个参数

  1. corePoolSize:核心线程数(最小线程数),一创建默认会有的线程数
  2. maximumPoolSize:最大线程数,当线程池中核心线程数都在执行任务时,会新建线程执行,最大的数目不会超过最大线程数
  3. keepAliveTime:线程(非核心线程,核心线程不会被销毁)执行完任务后多长时间没有再被使用,就会被销毁。
  4. unit:TimeUnit类型,指的是keepAliveTime的单位,时分秒等
  5. workQueue:阻塞对列
  6. threadFactory:线程工厂(一般自定义线程池的时候这个参数也不会动)
  7. handler:拒绝策略(有四种拒绝策略,后面会说,其实拒绝策略就是在超过最大线程数的情况下,还有任务提交,并且没有空闲的线程可用的情况下的处理情况)

这样我们明白了创建线程池的参数后,我们就发现

  • newSingleThreadExecutor()的核心线程数和最大线程数都是1,阻塞对列为
    默认大小的LinkedBlockingQueue<Runnable>()阻塞队列的长度为Integer.MAX_VALUE,所以可能因为阻塞队列过长而发生OOM
  • newFixedThreadPool(int nThreads)的核心线程数和最大线程数都是传入的参数nThreads,而且他也有newSingleThreadExecutor的阻塞队列过长而导致OOM的可能。
  • newCachedThreadPool()的核心线程数是0,而最大线程数是Integer.MAX_VALUE,所以这个线程池在很大的并发量下可能会因为创建大量线程而发生OOM,阻塞对列是new SynchronousQueue<Runnable>()同步阻塞队列

所以我们在使用线程池的时候,最好不要直接用Executors获取,最好是直接使用ThreadPoolExecutor获取自定义的线程池对象。

5、自定义一个线程池

自定义一个线程池,并且测试当任务数超过最大线程数+阻塞队列长度时,默认的拒绝策略会怎么做??

  //自定义一个线程池
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2,//核心线程数
                5,//最大线程数
                3,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(3),//长度为3阻塞队列
                Executors.defaultThreadFactory(),//默认线程工厂
                new ThreadPoolExecutor.AbortPolicy()//默认的拒绝策略AbortPolicy()不处理并且抛出异常
        );

            try {
                for (int i = 1; i <=9 ; i++) {
                    threadPoolExecutor.execute(() -> {
                        System.out.println(Thread.currentThread().getName() + "执行了");
                    });
                }
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                threadPoolExecutor.shutdown();//使用完一定要释放线程池资源
        }

执行结果:

pool-1-thread-1执行了
pool-1-thread-3执行了
pool-1-thread-3执行了
pool-1-thread-3执行了
pool-1-thread-4执行了
pool-1-thread-5执行了
pool-1-thread-2执行了
pool-1-thread-1执行了
java.util.concurrent.RejectedExecutionException: Task juctest.PoolTest$$Lambda$1/1096979270@7ba4f24f rejected from java.util.concurrent.ThreadPoolExecutor@3b9a45b3[Running, pool size = 5, active threads = 5, queued tasks = 3, 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 juctest.PoolTest.main(PoolTest.java:29)

结果可见,当我们自定义一个线程池并且使用AbortPolicy()拒绝策略的时候,阻塞队列满了的时候,丢弃任务,并且抛出异常。

其实我们的拒绝策略RejectedExecutionHandler接口有四个实现类,也就是四种拒绝策略

6、四种拒绝策略

6.1AbortPolicy()

丢弃任务,并且抛出异常

这是线程池默认的拒绝策略,在任务不能再提交的时候,抛出异常,及时反馈程序运行状态。如果是比较关键的业务,推荐使用此拒绝策略,这样子在系统不能承载更大的并发量的时候,能够及时的通过异常发现。

6.2DiscardPolicy()

丢弃任务,但是不抛出异常。如果线程队列已满,则后续提交的任务都会被丢弃,且是静默丢弃

6.3CallerRunsPolicy()

谁提交的任务,就交给哪个线程执行(谁叫你来的去找谁)

测试:

//自定义一个线程池
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2,//核心线程数
                5,//最大线程数
                3,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(3),//长度为3阻塞队列
                Executors.defaultThreadFactory(),//默认线程工厂
                new ThreadPoolExecutor.CallerRunsPolicy()//默认的拒绝策略AbortPolicy()不处理并且抛出异常
        );

            try {
                for (int i = 1; i <=9 ; i++) {
                    threadPoolExecutor.execute(() -> {
                        System.out.println(Thread.currentThread().getName() + "执行了");
                    });
                }
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                threadPoolExecutor.shutdown();//使用完一定要释放线程池资源
        }

结果:

pool-1-thread-1执行了
pool-1-thread-3执行了
pool-1-thread-2执行了
pool-1-thread-2执行了
pool-1-thread-5执行了
main执行了
pool-1-thread-3执行了
pool-1-thread-4执行了
pool-1-thread-1执行了

 由结果可以看出,由Main线程提交的任务,在队列满了的情况下,使用CallerRunsPolicy()拒绝策略的线程池,会再交给main线程去执行。

6.4DiscardOldestPolicy()

丢弃队列最前面的任务,然后重新提交被拒绝的任务(新任务入列)。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值