1. 创建线程有几种方式(必会)

1.继承 Thread 类并重写 run 方法创建线程,实现简单但不可以继承其他类
2.实现 Runnable 接口并重写 run 方法。避免了单继承局限性,编程更加灵活,实 现解耦。
3..实现 Callable 接口并重写 call 方法,创建线程。可以获取线程执行结果的返回 值,并且可以抛出异常。
4.使用线程池创建(使用 java.util.concurrent.Executor 接口)

2. Runnable 和 Callable 的区别?(必会)

主要区别 :

  1. Runnable 接口 run 方法无返回值;Callable 接口 call 方法有返回值,支持泛型
  2. Runnable 接口 run 方法只能抛出运行时异常,且无法捕获处理;Callable 接口 call 方 法允许抛出异常,可以获取异常信息

3. 如何启动一个新线程、调用start和run方法的区别?(必会)

线程对象调用 run 方法不开启线程。仅是对象调用方法。
线程对象调用 start 开启线程,并让 jvm 调用 run 方法在开启的线程中执行
调用 start 方法可以启动线程,并且使得线程进入就绪状态,
而 run 方法只是 thread 的一 个普通方法,还是在主线程中执行。

4. 为什么要使用多线程?多线程的优点和缺点是什么?

  1. 使用多线程是为了解决负载均衡问题,充分利用CPU资源.为了提高CPU的使用率,采用多线程的方式去同时完成几件事情而不互相干扰.为了处理大量的IO操作时或处理的情况需要花费大量的时间等等,比如:读写文件,视频图像的采集,处理,显示,保存等
  2. 多线程的好处:
    • 1.使用线程可以把占据时间长的程序中的任务放到后台去处理
    • 2.用户界面更加吸引人,这样比如用户点击了一个按钮去触发某件事件的处理,可以弹出一个进度条来显示处理的进度
    • 3.程序的运行效率可能会提高
    • 4.在一些等待的任务实现上如用户输入,文件读取和网络收发数据等,线程就比较有用了.
  3. 多线程的缺点:
    • 1.如果有大量的线程,会影响性能,因为操作系统需要在它们之间切换.
    • 2.更多的线程需要更多的内存空间
    • 3.线程中止需要考虑对程序运行的影响.
    • 4.通常块模型数据是在多个线程间共享的,需要防止线程死锁情况的发生

[

](https://blog.csdn.net/zhh1072773034/article/details/74240897)

5. 线程有哪几种状态以及各种状态之间的转换?(必会)

  1. 第一是 new->新建状态。在生成线程对象,并没有调用该对象的 start 方法,这是线程处于 创建状态。

  2. 第二是 Runnable->就绪状态。当调用了线程对象的 start 方法之后,该线程就进入了就绪 状态,但是此时线程调度程序还没有把该线程设置为当前线程,此时处于就绪状态。

  3. 第三是 Running->运行状态。线程调度程序将处于就绪状态的线程设置为当前线程,此时线 程就进入了运行状态,开始运行 run 函数当中的代码。

  4. 第四是阻塞状态。阻塞状态是线程因为某种原因放弃 CPU 使用权,暂时停止运行。直到线程 进入就绪状态,才有机会转到运行状态。阻塞的情况分三种: (1)等待 – 通过调用线程的 wait() 方法,让线程等待某工作的完成。 (2)超时等待 – 通过调用线程的 sleep() 或 join()或发出了 I/O 请求时,线程会进入到阻塞状态。 当 sleep()状态超时、join()等待线程终止或者超时、或者 I/O 处理完毕时,线程重新转入就绪状 态。 (3)同步阻塞 – 线程在获取 synchronized 同步锁失败(因为锁被其它线程所占用),它会进入同 步阻塞状态。

  5. 第五是 dead->死亡状态: 线程执行完了或者因异常退出了 run()方法,该线程结束生命周期.

图示:
image.png

6. 线程相关的基本方法?(必会)

线程相关的基本方法有 wait,notify,notifyAll,sleep,join,yield 等

1.线程等待(wait)
调用该方法的线程进入 WAITING 状态,只有等待另外线程的通知或被中 断才会返回,需要注意的是调用 wait()方法后,会释放对象的锁。因此,wait 方 法一般用在同步方法或同步代码块中。

2.线程睡眠(sleep)
sleep 导致当前线程休眠,与 wait 方法不同的是 sleep 不会释放当前占 有的锁,sleep(long)会导致线程进入 TIMED-WATING 状态,而 wait()方法 会导致当前线程进入 WATING 状态.

3.线程让步(yield)
yield 会使当前线程让出 CPU 执行时间片,与其他线程一起重新竞争 CPU 时间片。一般情况下,优先级高的线程有更大的可能性成功竞争得到 CPU 时间片,但这又不是绝对的,有的操作系统对 线程优先级并不敏感。

4.线程中断(interrupt)
中断一个线程,其本意是给这个线程一个通知信号,会影响这个线程内部的 一个中断标识位。这个线程本身并不会因此而改变状态(如阻塞,终止等)

5.Join 等待其他线程终止
join() 方法,等待其他线程终止,在当前线程中调用一个线程的 join() 方 法,则当前线程转为阻塞状态,回到另一个线程结束,当前线程再由阻塞状态变 为就绪状态,等待 cpu 的宠幸

6.线程唤醒(notify)
Object 类中的 notify() 方法,唤醒在此对象监视器上等待的单个线程,如 果所有线程都在此对象上等待,则会选择唤醒其中一个线程,选择是任意的,并 在对实现做出决定时发生,线程通过调用其中一个 wait() 方法,在对象的监视 器上等待,直到当前的线程放弃此对象上的锁定,才能继续执行被唤醒的线程, 被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争。类 似的方法还有 notifyAll() ,唤醒再次监视器上等待的所有线程。

7. wait()和 sleep()的区别?(必会)

1. 来自不同的类
wait():来自 Object 类;
sleep():来自 Thread 类;

2.关于锁的释放:
wait():在等待的过程中会释放锁;
sleep():在等待的过程中不会释放锁

3.使用的范围:
wait():必须在同步代码块中使用;
sleep():可以在任何地方使用;

4.是否需要捕获异常
wait():不需要捕获异常;
sleep():需要捕获异常;

8. 线程池

1. 线程池的分类(高薪常问)

image.png1. newCachedThreadPool:创建一个可进行缓存重复利用的线程池
2. newFixedThreadPool:创建一个可重用固定线程数的线程池,以共享的无 界队列方式来运行这些线程,线程池中的线程处于一定的量,可以很好的控制线程的并发量
3. newSingleThreadExecutor : 创建一个使用单个worker 线 程 的 Executor ,以无界队列方式来运行该线程。线程池中最多执行一个线程,之后提交的线程 将会排在队列中以此执行
4. newSingleThreadScheduledExecutor: 创建一个单线程执行程序,它可安排在给定延迟后运行命令或者定期执行
5. newScheduledThreadPool:创建一个线程池,它可安排在给定延迟后运行 命令或者定期的执行
6. newWorkStealingPool:创建一个带并行级别的线程池,并行级别决定了 同一时刻最多有多少个线程在执行,如不传并行级别参数,将默认为当前系统的 CPU个数

2. 线程池核心参数(高薪常问) -ps 重点

  1. corePoolSize:核心线程池的大小
  2. maximumPoolSize:线程池能创建线程的最大个数
  3. keepAliveTime:空闲线程存活时间
  4. unit:时间单位,为 keepAliveTime 指定时间单位
  5. workQueue:阻塞队列,用于保存任务的阻塞队列
  6. threadFactory:创建线程的工程类
  7. handler:饱和策略(拒绝策略)

3. 线程池的原理(高薪常问)

image.png
当一个任务提交至线程池之后,
1. 线程池首先判断核心线程池里的线程是否已经满了。
如果不是,则创建一个新的工作线 程来执行任务。否则进入 2.
2. 判断工作队列是否已经满了,倘若还没有满,将线程放入工作队列。否则进入 3.
3. 判断线程池里的线程是否都在执行任务。如果不是,则创建一个新的工作线程来执行。 如果线程池满 了,则交给饱和策略来处理任务。

拒绝策略(了解)

  1. ThreadPoolExecutor.AbortPolicy(系统默认): 丢弃任务并抛出 RejectedExecutionException 异常,让你感知到任务被拒绝了,我们可以根据业务逻辑选 择重试或者放弃提交等策略
  2. ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常,相对而 言存在一定的风险,因为我们提交的时候根本不知道这个任务会被丢弃,可能造成数据丢失。
  3. ThreadPoolExecutor.DiscardOldestPolicy: 丢弃队列最前面的任务,然后重新尝试执 行任务(重复此过程),通常是存活时间最长的任务,它也存在一定的数据丢失风险
  4. ThreadPoolExecutor.CallerRunsPolicy:既不抛弃任务也不抛出异常,而是将某些任务 回退到调用者,让调用者去执行它。


4. 线程池介绍及创建线程池的4种方式

1. 什么是线程池

Java中的线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的程序
都可以使用线程池。在开发过程中,合理地使用线程池能够带来3个好处。
第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
第三:提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,
还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。但是,要做到合理利用
线程池,必须对其实现原理了如指掌。

2. 线程池作用

线程池是为突然大量爆发的线程设计的,通过有限的几个固定线程为大量的操作服务,减少了创建和销毁线程所需的时间,从而提高效率。
如果一个线程的时间非常长,就没必要用线程池了(不是不能作长时间操作,而是不宜.),况且我们还不能控制线程池中线程的开始、挂起、和中止。

3.线程池四种创建方式

Java通过Executors(jdk1.5并发包)提供四种线程池,分别为:

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

    (1) 为什么需要线程池

    java中为了提高并发度,可以使用多线程共同执行,但是如果有大量线程短时间之内被创建和销毁,会占用大量的系统时间,影响系统效率。
    为了解决上面的问题,java中引入了线程池,可以使创建好的线程在指定的时间内由系统统一管理,而不是在执行时创建,执行后就销毁,从而避免了频繁创建、销毁线程带来的系统开销。

(2) 线程池的处理流程

以ThreadPoolExecutor为例,当我们把一个Runnable交给线程池去执行的时候,这个线程池处理的流程是这样的:

  1. 先判断线程池中的核心线程们是否空闲,如果空闲,就把这个新的任务指派给某一个空闲线程去执行。如果没有空闲,并且当前线程池中的核心线程数还小于 corePoolSize(核心线程数),那就再创建一个核心线程。


  1. 如果线程池的线程数已经达到核心线程数,并且这些线程都繁忙,就把这个新来的任务放到等待队列中去。如果等待队列又满了,那么查看一下当前线程数是否到达maximumPoolSize(线程池能创建线程的最大个数),如果还未到达,就继续创建线程。


  1. 如果已经到达了,就交给RejectedExecutionHandler(拒绝策略)来决定怎么处理这个任务。


(3) 构造器中各个参数的含义:

1.corePoolSize(线程池的基本大小)
当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。如果调用了线程池的prestartAllCoreThreads方法,线程池会提前创建并启动所有基本线程。

2.runnableTaskQueue(任务队列)
用于保存等待执行的任务的阻塞队列。可以选择以下几个阻塞队列。

  • ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。
  • LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。
  • SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue。
  • PriorityBlockingQueue:一个具有优先级得无限阻塞队列。

3.maximumPoolSize(线程池最大大小)
线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。值得注意的是如果使用了无界的任务队列这个参数就没什么效果。

4.ThreadFactory:用于设置创建线程的工厂
可以通过线程工厂给每个创建出来的线程设置更有意义的名字,Debug和定位问题时非常又帮助。

5.RejectedExecutionHandler(饱和策略,拒绝策略)
当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。这个策略默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。以下是JDK1.5提供的四种策略。n AbortPolicy:直接抛出异常。

  • CallerRunsPolicy:只用调用者所在线程来运行任务。
  • DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
  • DiscardPolicy:不处理,丢弃掉。
  • 当然也可以根据应用场景需要来实现RejectedExecutionHandler接口自定义策略。如记录日志或持久化不能处理的任务。

6.keepAliveTime(线程活动保持时间)
线程池的工作线程空闲后,保持存活的时间。所以如果任务很多,并且每个任务执行的时间比较短,可以调大这个时间,提高线程的利用率。

7.TimeUnit(线程活动保持时间的单位)
可选的单位有天(DAYS),小时(HOURS),分钟(MINUTES),毫秒(MILLISECONDS),微秒(MICROSECONDS, 千分之一毫秒)和毫微秒(NANOSECONDS, 千分之一微秒)。

(4) 线程池推荐配置

  • corePoolSize = 1
  • queueCapacity = Integer.MAX_VALUE
  • maxPoolSize = Integer.MAX_VALUE
  • keepAliveTime = 60秒
  • allowCoreThreadTimeout = false
  • rejectedExecutionHandler = AbortPolicy()
    个性配置
    corePoolSize 建议值为:每秒任务数任务执行时间(例如0.5s) 【100 0.2=20】
    maxPoolSize 建议和corePoolSize 配置一样、有同学建议直接设置为cpu数量+1
    keepAliveTiime 设定值可根据任务峰值持续时间来设定。
    其余配置可以走默认值,也可根据情况配置