线程池

 

1.聊聊线程和进程

线程是处于进程之中,每个进程都有独立的内存空间,而每个线程都共享一片内存,但是每个线程都有单独的栈内存,用于存储本地数据。

1.1创建线程的方式有哪几种?

  1. 通过继承Thread类
  2. 通过实现Runnable接口
  3. 通过实现Callable接口
  4. 通过线程池来创建
  5. 通过Java8新特性lambda表达式

1.2 start()和run()的区别

start():当执行start()方法的时候会启动一个线程,然后执行对应的run(),但是start()方法只会执行一次

run():该方法和普通的方法一样,能够执行多次。单独调用run()方法的话,会在当前线程中执行run(),不会启动会新线程

1.3 Calleble接口和Runable接口的区别

1.Callable规定的方法是call(),Runnable规定的方法是run().

2.Callable的任务执行后可返回值,而Runnable的任务是不能返回值得

3.call方法可以抛出异常,run方法不可以

2. 线程池

2.1 为什么要用线程池?

线程池顾名思义就是用来管理线程的池子,线程池的主要工作就是控制运行的线程的数量,处理过程中将任务放入队列中,然后线程创建后,就启动这些任务。如果线程数量超过了最大线程数就排队等候,等其他线程执行完毕后,就从队列里面取出任务来执行。

线程池的优点:

  • 降低资源消耗。重复利用已经创建的线程能够降低线程创建和销毁造成的销毁
  • 提高响应速度。任务到达时,无需等待线程创建就可立即执行任务
  • 提高线程的管理性。线程是稀缺资源,创建线程和销毁线程会影响系统性能。如果无休止创建,不仅消耗系统资源,还影响系统的稳定性,通过线程池进行统一的分配、调优、监控。

2.2 架构说明

Java中的线程池是通过Executor框架实现的,该框架使用了Executor,Executors(代表工具类),ExecutorService,ThreadPoolExecutor这几个类

image-20200317175202647

image-20200317175241007

 

2.3 创建线程池

  1. Executors.newFixedThreadPool(int i) :创建一个拥有 i 个线程的线程池。创建固定线程数量的线程池,可以控制最大并发线程数,超出的线程在队列中排队等候
  2. Executors.newSingleThreadExecutor:创建一个只有一个线程的单线程池。一个任务一个任务执行的场景,保证所有任务按照指定顺序执行
  3. Executors.newCacheThreadPool():创建一个可扩容的线程池。执行很多短期异步的小程序或者负载较轻的小程序。创建一个可缓存的线程池,如果线程长度超过处理需要,可收回空闲线程。
  4. Executors.newScheduledThreadPool(int corePoolSize):线程池支持定时以及周期性执行任务,创建一个corePoolSize即核心线程数作为传入参数,最大线程数为整形的最大数的线程池

具体使用,首先我们需要使用Executors工具类,进行创建线程池,这里创建了一个拥有5个线程的线程池

// 一池5个处理线程(用池化技术,一定要记得关闭)
ExecutorService threadPool = Executors.newFixedThreadPool(5);

// 创建一个只有一个线程的线程池
ExecutorService threadPool = Executors.newSingleThreadExecutor();

// 创建一个拥有N个线程的线程池,根据调度创建合适的线程
ExecutorService threadPool = Executors.newCacheThreadPool();

执行下面的业务场景:

10个用户来办理业务,每个用户就是一个来自外部请求线程

我们需要使用 threadPool.execute执行业务,execute需要传入一个实现了Runnable接口的线程:

threadPool.execute(() -> {
	System.out.println(Thread.currentThread().getName() + "\t 给用户办理业务");
});

使用完毕后关闭线程池:

threadPool.shutdown();

完整代码为:

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

        // Array  Arrays(辅助工具类)
        // Collection Collections(辅助工具类)
        // Executor Executors(辅助工具类)


        // 一池5个处理线程(用池化技术,一定要记得关闭)
        ExecutorService threadPool = Executors.newFixedThreadPool(5);

        // 模拟10个用户来办理业务,每个用户就是一个来自外部请求线程
        try {

            // 循环十次,模拟业务办理,让5个线程处理这10个请求
            for (int i = 0; i < 10; i++) {
                final int tempInt = i;
                threadPool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + "\t 给用户:" + tempInt + " 办理业务");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPool.shutdown();
        }

    }
}

执行结果:

pool-1-thread-1	 给用户:0 办理业务
pool-1-thread-5	 给用户:4 办理业务
pool-1-thread-1	 给用户:5 办理业务
pool-1-thread-4	 给用户:3 办理业务
pool-1-thread-2	 给用户:1 办理业务
pool-1-thread-3	 给用户:2 办理业务
pool-1-thread-2	 给用户:9 办理业务
pool-1-thread-4	 给用户:8 办理业务
pool-1-thread-1	 给用户:7 办理业务
pool-1-thread-5	 给用户:6 办理业务

2.4 底层实现

我们通过查看源码,点击了Executors.newSingleThreadExecutor 和 Executors.newFixedThreadPool能够发现底层都是使用了ThreadPoolExecutor

image-20200317182004293

我们可以看到线程池的内部,还使用到了LinkedBlockingQueue链表阻塞队列

同时在查看Executors.newCacheThreadPool 看到底层使用的是SynchronousBlockingQueue阻塞队列

最后查看一下,完整的三个创建线程的方法

image-20200317183202992

2.5 线程池的七大参数

image-20200317183600957

线程池在创建的时候,有七大参数:

  • corePoolSize:核心线程数,线程池的默认线程数量。
  • maximumPoolSize:线程池中的最大线程数量,此值必须大于等于1
  • keepAliveTime:多余的空闲线程存活时间。当线程池的数量超过corePoolSize时,当空闲时间达到corePoolSize,多余的空闲线程会被销毁,只剩下核心线程数为止。默认情况下,只有当线程池中的线程数量大于核心线程数,keepAliveTime才会起作用
  • unit:keepAliveTime的单位
  • workQueue:工作队列,被提交但未执行的任务(类似于银行中候客区)。
  • threadFactory:表示在线程池中生产工作线程的线程工厂,用于创建线程池,一般用默认即可
  • handler:拒绝策略,表示当队列满了并且工作线程大于线程池的最大线程数时,用于拒绝线程请求的策略

2.6 拒绝策略

拒绝策略都实现了RejectedExecutionHandler接口

  • AbortPolicy:默认,直接抛出RejectedExcutionException异常,阻止系统正常运行
  • DiscardPolicy:直接丢弃任务,不予任何处理也不抛异常,如果允许任务丢失,是一种好的解决方案
  • DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列尝试再次提交当前任务
  • CallerRunsPolicy:既不抛异常也不丢去任务,而是将某些任务回退给调用者

2.7 线程池的底层工作原理

线程池运行架构图

image-20200318154414717

文字说明:

  1. 在创建了线程池后,等待提交过来的任务请求
  2. 当调用execute()方法添加一个请求任务,线程池会做出如下判断: 
  3. 如果正在运行的线程数量小于核心线程数,那么会马上创建线程运行任务
  4. 如果正在运行的线程数量大于等于核心线程数,那么就将任务放入队列
  5. 如果这时候队列满了,但是正在运行的线程数量还小于最大线程数,那么启动非核心线程数运行任务
  6. 如果这时候队列满了并且线程运行数量大于最大线程数,此时线程池会启用饱和的拒绝策略
  7. 当一个线程完成任务时,就会从队列中取出任务来运行
  8. 当线程无事可做到一定时间(keepAliveTime),线程池会做如下判断:
  9. 如果当前运行线程大于核心线程数,那么这个线程就会被停掉
  10. 如果线程池中的所有任务完成后,那么会恢复到核心线程数

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值