1、线程的生命周期

image.png
image.png

2、线程的创建方式

方式1:继承 Thread 类

步骤:
1、创建一个继承于Thread类的子类;
2、重写Thread类的run(),将此线程执行的操作声明在run()中;
3、创建Thread类的子类的对象;
4、通过此对象调用start()

注意点:

  • 我们不能通过直接调用run()的方式启动线程。
  • 如果再启动一个线程,必须重新创建一个Thread子类的对象,调用此对象的start(),不可以还让已经start()的线程去执行,会报IllegalThreadStateException

【示例代码】

  1. // 1. 创建一个继承于Thread类的子类
  2. class MyThread1 extends Thread {
  3. // 2. 重写Thread类的run()
  4. @Override
  5. public void run() {
  6. for (int i = 0; i <= 100; i++) {
  7. if (i % 2 == 0) {
  8. System.out.println(Thread.currentThread().getName() + ": " + i);
  9. }
  10. }
  11. }
  12. }
  13. public class ThreadTest1 {
  14. public static void main(String[] args) {
  15. // 3. 创建Thread类的子类的对象
  16. MyThread1 t1 = new MyThread1();
  17. t1.setName("线程1");
  18. // 4.通过此对象调用start():
  19. // ①启动当前线程 ② 调用当前线程的run()
  20. t1.start();
  21. // 再启动一个线程
  22. MyThread1 t2 = new MyThread1();
  23. t2.setName("线程2");
  24. t2.start();
  25. }
  26. }

方式2:实现 Runnable 接口

创建步骤
1、创建一个实现了 Runnable 接口的类;
2、实现类去实现 Runnable 中的抽象方法:run()
3、创建实现类的对象;
4、将此对象作为参数传递到 Thread 类的构造器中,创建 Thread 类的对象;
5、通过 Thread 类的对象调用 start()

【示例代码】

  1. // 1. 创建一个实现了Runnable接口的类
  2. class MyThread implements Runnable {
  3. // 2. 实现类去实现Runnable中的抽象方法:run()
  4. @Override
  5. public void run() {
  6. for (int i = 0; i < 100; i++) {
  7. if (i % 2 == 0) {
  8. System.out.println(Thread.currentThread().getName() + ": " +i);
  9. }
  10. }
  11. }
  12. }
  13. public class ThreadTest {
  14. public static void main(String[] args) {
  15. // 3. 创建实现类的对象
  16. MyThread myThread = new MyThread();
  17. // 4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
  18. Thread t1 = new Thread(myThread);
  19. t1.setName("线程1");
  20. // 5. 通过Thread类的对象调用start():
  21. // ① 启动线程 ②调用当前线程的run()-->调用了Runnable类型的target的run()
  22. t1.start();
  23. // 再启动一个线程:
  24. Thread t2 = new Thread(myThread);
  25. t2.setName("线程2");
  26. t2.start();
  27. }
  28. }

继承方式与实现方式的比较:

  • 开发中优先选择实现Runnable接口的方式。原因:
    • 实现的方式没有类的单继承性的局限性。
    • 实现的方式更适合来处理多个线程有共享数据的情况。
  • 联系:public class Thread implements Runnable
  • 相同点:两种方式都需要重写run(),将线程要执行的逻辑声明在run()中。

方式3:实现 Callable 接口

创建步骤
1、创建一个实现Callable接口的实现类;
2、实现call()方法,将此线程需要执行的操作声明在call()中;
3、创建Callable接口实现类的对象,并将此Calllable接口实现类的对象作为参数传递到FutureTask构造器中,创建FutureTask对象;
4、将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()方法;
5、获取Callablecall()方法的返回值,get()方法的返回值即为FutureTask构造器参数Callable实现类重写的call()方法的返回值。

如何理解实现**Callable**接口的方式创建多线程比实现**Runnable**接口创建多线程方式强大?
1、call()可以有返回值的。
2、call()可以抛出异常,被外面的操作捕获,获取异常的信息。
3、Callable是支持泛型的。

【示例代码】

  1. // 1.创建一个实现Callable的实现类
  2. class NumThread implements Callable<Integer> {
  3. // 2.实现call方法,将此线程需要执行的操作声明在call()中
  4. @Override
  5. public Integer call() throws Exception {
  6. int sum = 0;
  7. for (int i = 0; i <=100; i++) {
  8. if (i % 2 == 0) {
  9. System.out.println(i);
  10. sum += i;
  11. }
  12. }
  13. return sum;
  14. }
  15. }
  16. public class ThreadNew {
  17. public static void main(String[] args) {
  18. // 3.创建Callable接口实现类的对象
  19. NumThread numThread = new NumThread();
  20. // 4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
  21. FutureTask<Integer> futureTask = new FutureTask<Integer>(numThread);
  22. // 5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
  23. new Thread(futureTask).start();
  24. try {
  25. // 6.获取Callable中call方法的返回值
  26. // get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。
  27. Integer sum = futureTask.get();
  28. System.out.println("总和为:" + sum);
  29. } catch (InterruptedException e) {
  30. e.printStackTrace();
  31. } catch (ExecutionException e) {
  32. e.printStackTrace();
  33. }
  34. }
  35. }

方式4:使用线程池

1、创建线程池的四种方法

如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程 就会大大降低 系统的效率,因为频繁创建线程和销毁线程需要时间. 线程池就是一个容纳多个线程的容 器,池中的线程可以反复使用,省去了频繁创建线程对象的操作,节省了大量的时间和资源。
使用线程池的好处:降低资源消耗,提高响应速度,提高线程的可管理性。
Java通过Executors提供四种线程池,分别为:

  • newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
  • newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
  • newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
  • newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

    2、创建步骤

    1、提供指定线程数量的线程池;
    2、设置线程池的属性;
    3、执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象;
    4、关闭连接池。

3、【示例代码】

  1. class NumberThread1 implements Runnable {
  2. @Override
  3. public void run() {
  4. for (int i = 0; i <= 100; i++) {
  5. if (i % 2 == 0) {
  6. System.out.println(Thread.currentThread().getName() + ": " + i);
  7. }
  8. }
  9. }
  10. }
  11. class NumberThread2 implements Runnable {
  12. @Override
  13. public void run() {
  14. for (int i = 0; i <= 100; i++) {
  15. if (i % 2 != 0) {
  16. System.out.println(Thread.currentThread().getName() + ": " + i);
  17. }
  18. }
  19. }
  20. }
  21. public class ThreadPoolTest {
  22. public static void main(String[] args) {
  23. //1.提供指定线程数量的线程池
  24. ExecutorService service = Executors.newFixedThreadPool(10);
  25. ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
  26. //设置线程池的属性
  27. // System.out.println(service.getClass());
  28. // service1.setCorePoolSize(15);
  29. // service1.setKeepAliveTime();
  30. //2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
  31. service.execute(new NumberThread1());
  32. service.execute(new NumberThread2());
  33. //3.关闭连接池
  34. service.shutdown();

3、线程池的作用

线程池是一种基于池化思想管理线程的工具

  • 降低资源消耗,提高线程利用率,降低创建和销毁线程的消耗
  • 提高响应速度,任务来了,直接有线程可用可执行,而不是先创建线程,在执行
  • 提高线程的可管理性,线程是稀缺资源,使用线程池可统一分配调度监控。

    线程池解决的问题

    线程池解决的核心问题就是资源管理问题,在并发环境下,系统不能确定在任意时刻中,有多少任务需要执行,就有多少资源投入。为解决资源分配问题,线程池采用了“池化思想”,是为了最大化收益,最小风险,而将资源统一在一起管理的一种思想。常见的有:

  • 内存池:预先申请内存,提升申请内存速度,减少内存碎片。

  • 连接池:预先申请数据库连接,提升申请连接的速度,降低系统的开销
  • 实例池:循环使用对象,减少资源在初始化和释放时的昂贵损坏。

    4、线程池参数解释

    ```java public ThreadPoolExecutor(int corePoolSize,
    1. int maximumPoolSize,
    2. long keepAliveTime,
    3. TimeUnit unit,
    4. BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
    1. Executors.defaultThreadFactory(), defaultHandler);
    }

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0) throw new IllegalArgumentException(); if (workQueue == null || threadFactory == null || handler == null) throw new NullPointerException(); this.acc = System.getSecurityManager() == null ? null : AccessController.getContext(); this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.workQueue = workQueue; this.keepAliveTime = unit.toNanos(keepAliveTime); this.threadFactory = threadFactory; this.handler = handler; }

  1. - **corePoolSize** - 核心线程数,正常情况下创建工作的线程数,这些线程创建后并不会消除,而是一种常驻线程
  2. - **maximumPoolSize** - 最大线程数,与核心线程数对应,表示最大允许被创建的线程数,当任务很多,核心线程数不够用,队列已满,此时就会创建新的线程。
  3. 为什么是先添加队列,在创建最大线程?<br />在创建线程的时候是需要获取全局锁的,这个时候其他就是阻塞,影响整体效率
  4. - **keepAliveTime** - 表示超出核心线程数之外的线程的空闲存活时间,核心线程不会消除,但是超出核心线程数的部分线程如果空闲一定时间则会被消除。
  5. - **workQueue** - 用来存放待执行任务的队列,默认为BlockingQueue(阻塞队列)。
  6. 为什么使用阻塞队列:<br />一般队列只是一个长度有限的缓冲区,若超出长度,就无法保留当前任务,阻塞队列通过阻塞可保留当前想要继续入队的任务<br />阻塞队列可以保证队列中没有任务的时候,阻塞获取任务的线程进入wait状态,释放cpu资源。<br />阻塞队列自带阻塞和唤醒功能,不需要额外处理,无任务时,线程池利用阻塞队列的take方法挂起,从而维持核心线程存 货,不至于一直占用cpu资源
  7. - **ThreadFactory** - 线程工厂,用来生产线程,我们可以选择默认工厂,产生的线程都在同一个组内,拥有相同优先级,且都不是守护线程。
  8. - **RejectedExecutionHandler **- 任务拒绝策略,第一种:当我们调用shutdown等方法关闭线程池后,这时候再向线程池提交任务就会遭到拒绝;另一种情况,当达到最大线程数,线程池已经没有能力继续处理新提交的任务时,也拒绝。
  9. <a name="aFzLi"></a>
  10. ### 拒绝策略
  11. | 拒绝策略 | 说明 |
  12. | --- | --- |
  13. | AbortPolicy | 默认策略,不执行此任务,直接抛出异常 |
  14. | DiscardOldestPolicy | 丢弃队列最前面的任务,然后重新执行此任务 |
  15. | DiscardPolicy | 丢弃任务但不抛出异常 |
  16. | CallerRunsPolicy | 由调度线程处理该任务 |
  17. | 自定义策略 | 自定义策略 |
  18. <a name="RoxJn"></a>
  19. ## 5、线程池流程处理
  20. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/1161651/1616124536923-2498f3f0-3b27-42a0-95f4-fa5b202817e6.png#crop=0&crop=0&crop=1&crop=1&height=658&id=b5asI&margin=%5Bobject%20Object%5D&name=image.png&originHeight=715&originWidth=488&originalType=binary&ratio=1&rotation=0&showTitle=false&size=54492&status=done&style=none&title=&width=449)
  21. - 将任务提交给线程池
  22. - 如果线程池中线程数小于核心线程数,则创建一个新的线程来执行该任务
  23. - 提交任务时,线程池中的空闲的线程数为0并且线程数等于核心线程数,则观察线程池中的任务队列是否已满,如果未满则将任务添加到任务队列
  24. - 如果最大线程数大于核心线程数,并且总线程数小于最大线程数,则创建一个新的线程来执行该任务。
  25. - 当任务队列已满时,就执行拒绝策略
  26. <a name="dMM6N"></a>
  27. ## 6、线程池的核心设计与实现
  28. <a name="KyFu0"></a>
  29. ### 设计
  30. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/1161651/1616640762999-c561f642-e6e1-4b1b-8c08-d25c7634d0fd.png#crop=0&crop=0&crop=1&crop=1&height=314&id=HYMab&margin=%5Bobject%20Object%5D&name=image.png&originHeight=314&originWidth=248&originalType=binary&ratio=1&rotation=0&showTitle=false&size=9345&status=done&style=none&title=&width=248)<br />线程池的核心实现类是ThreadPoolExecutor,顶层接口是Executor, 顶层接口的设计思想是:**将任务提交和任务执行进行解耦**,用户无需关注如何创建线程,如何调度线程来执行任务。
  31. ```java
  32. public interface Executor {
  33. void execute(Runnable command);
  34. }

ExecutorService接口增加了一些能力:

  • 扩充执行任务的能力,补充可以为一个或一批任务生成Future方法
  • 提供了管理线程池的方法,比如停止线程池的运行

    1. public interface ExecutorService extends Executor {
    2. void shutdown();
    3. List<Runnable> shutdownNow();
    4. boolean isShutdown();
    5. boolean isTerminated();
    6. boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;
    7. <T> Future<T> submit(Callable<T> task);
    8. <T> Future<T> submit(Runnable task, T result);
    9. Future<?> submit(Runnable task);
    10. <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
    11. long timeout, TimeUnit unit)
    12. throws InterruptedException;
    13. }

    AbstractExecutorService则是上层的抽象类,将执行任务的流程串联起来,保证下层的实现只需关注一个执行任务的方法即可,最下层的实现类ThreadPoolExecutor实现最复杂的运行部分,一方面维护自身的生命周期,另一方面同时管理线程和任务,使两者良好的结合从而执行并行任务。
    image.png

    生命周期管理

    线程池内部使用一个变量维护两个值:运行状态runState和线程数量workerCount

    1. private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

    ThreadPoolExecutor的运行有5种状态

运行状态 状态描述
RUNNING 能接受新提交的任务,并且也能处理阻塞队列中的任务
SHUTDOWN 关闭状态,不再接受新提交的任务,却可以继续处理阻塞队列中已保存的任务
STOP 不能接受任务,也不处理队列中的任务,会中断正在处理任务的线程
TIDYING 所有的任务都已终止了,workerCount(有效线程数)为0
TERMINATED 在terminated()方法执行完成后进入该状态

生命周期状态转换
image.png

Worker

线程池为了掌握线程的状态并维护线程的生命周期,设计了线程池内的工作线程Worker

  1. private final class Worker extends AbstractQueuedSynchronizer implements Runnable{
  2. final Thread thread;//Worker持有的线程
  3. Runnable firstTask;//初始化的任务,可以为null
  4. }

image.png

线程池中线程复用的原理

线程池将线程和任务进行解耦,线程是线程,任务是任务,通过Worker实现,摆脱了之前通过thread创建线程时的一个线程必须对应一个任务的限制
在线程池中,同一个线程可以从阻塞队列中不断获取新任务来执行,其核心原理在于线程池对 Thread 进行了封装,并不是每次执行任务都会调用 Thread.start() 来创建新线程,而是让每个线程去 执行一个“循环任务”,在这个“循环任务”中不停检查是否有任务需要被执行,如果有则直接执行,也就 是调用任务中的 run 方法,将 run 方法当成一个普通的方法执行,通过这种方式只使用固定的线程就 将所有任务的 run 方法串联起来。

参考:https://juejin.cn/post/6880790530653945869