多线程
多线程创建有哪些方法
- 继承 Thread 类
重写 run()
调用 start() - 实现 Runnable 接口
重写 run() 方法
将类作为任务交给Thread类执行
调用 start() - 实现 Callable 接口
重写 call()
将类作为任务,用 FutureTask 进行封装为线程任务对象
将线程任务对象作为任务交给Thread类执行
调用 start()
调用 FutureTask.get() 返回结果
继承 Thread 类有什么缺点
已经继承一个类,无法继承其他类,不利于拓展
Thread 类的 run() 和 start() 有什么区别
- 通过调用线程类的start()方法来启动一个线程,使线程处于就绪状态,即可以被JVM来调度执行,在调度过程中,JVM通过调用线程类的run()方法来完成实际的业务逻辑,当run()方法结束后,此线程就会终止。
- 如果直接调用线程类的run()方法,会被当作一个普通的函数调用,程序中仍然只有主线程这一个线程。即start()方法能够异步的调用run()方法,但是直接调用run()方法却是同步的,无法达到多线程的目的。
- 因此,只用通过调用线程类的start()方法才能达到多线程的目的。
Runnable 接口优点和缺点
优点:可扩展
缺点:无返回结果
Callable 接口优点和缺点
优点:可拓展,可返回结果
缺点:编码复杂
最佳实践:如何实现多线程(无线程池)
无返回结果使用 Runnable 及其衍生类
有返回结果使用 Callable ,FutureTask 及其衍生类
当有多个线程在执行,要如何区分
获取当前线程:getCurrentThread()
使用名称区分:getName()
Thread 常用 API
线程安全
线程安全出现的本质原因
多个线程同时操作同一个共享资源
如何解决线程安全
线程同步
线程同步
如何实现线程同步
加锁:让多个线程实现先后依次访问共享资源,这样就解决了安全问题
同步代码块如何实现
同步代码块的锁对象要求
理论上:锁对象只要对于当前同时执行的线程来说是同一个对象即可
同步方法如何实现
同步方法的锁对象原理
同步方法其实底层也是有隐式锁对象的,只是锁的范围是整个方法代码。
如果方法是实例方法:同步方法默认用this作为的锁对象。但是代码要高度面向对象!
如果方法是静态方法:同步方法默认用类名.class作为的锁对象。
最佳实践:锁对象如何选取
- 规范上:建议使用共享资源作为锁对象。
- 对于实例方法建议使用this作为锁对象。
- 对于静态方法建议使用字节码(类名.class)对象作为锁对象
最佳实践:同步代码块还是同步方法
代码块的范围更小,最好选择代码块
线程通信
线程通信的前提:线程通信通常是在多个线程操作同一个共享资源的时候需要进行通信,且要保证线程安全
线程池
为什么需要线程池
每次请求,后台都需要创建新的线程,创建的开销比较大
创建一个线程池,每次请求过来,就复用线程池中的空闲线程,不用新建
有哪些线程池的相关类
ExecutorService 线程池接口
ThreadPoolExecutor 实现了 ExecutorService
Executors 线程池的工具类
如何使用ThreadPoolExecutor创建线程池
调用构造器
示例
参数一:指定线程池的线程数量(核心线程): corePoolSize
参数二:指定线程池可支持的最大线程数: maximumPoolSize
参数三:指定临时线程的最大存活时间: keepAliveTime
参数四:指定存活时间的单位(秒、分、时、天): unit
参数五:指定任务队列: workQueue,存放提交但未执行任务的队列
参数六:指定用哪个线程工厂创建线程: threadFactory
参数七:指定线程忙,任务满的时候,新任务来了怎么办,等待队列满后的拒绝策略: handler
一个任务被提交到线程池以后,首先会找有没有空闲存活线程,如果有则直接将任务交给这个空闲线程来执行,如果没有则会缓存到工作队列(后面会介绍)中,如果工作队列满了,才会创建一个新线程
临时线程什么时候创建
新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程。
什么时候会开始拒绝任务
核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始任务拒绝
线程池的任务提交过程
- 任务提交,查看核心线程是否已经满(corePoolSize),没满就使用核心线程
- 核心线程已满,就查看工作队列(workQueue),没满就加入到工作队列
- 工作队列已满,就查看最大线程数(maximumPoolSize),没满就创建临时线程
- 最大线程数已满,则使用拒绝策略
线程池执行Runnable对象是用什么方法
void execute(Runnable command)
无返回
线程池执行 Callable 对象是用什么方法
Future submit(Callable task)
有返回结果
拒绝策略
默认:丢弃任务并抛出RejectedExecutionException异常
使用 Executors 快速创建线程池
最常用:
public static ExecutorService newFixedThreadPool(int nThreads)
Executors 创建的线程池有什么问题
总体来说,就是没有对,工作队列长度、最大线程数,进行限制,会导致 OOM 。
面试:Executors 底层使用了什么类实现
ThreadPoolExecutor
最佳实践:创建线程池应该使用ThreadPoolExecutor 还是 Executors
使用 Executors 会有 OOM 风险
最好使用 ThreadPoolExecutor ,定义时指定工作队列长度和最大线程长度。
定时器
什么是定时器,在项目中有什么作用
定时器是一种控制任务延时调用,或者周期调用的技术
可以用来定时调用整个系统的检查函数
如何实现定时器
-
Timer
-
ScheduledExecutorService
Timer有什么问题
- 底层使用单线程,处理多个任务按照顺序执行,存在延时与设置定时器的时间有出入
- 可能因为其中的某个任务的异常使Timer线程死掉,从而影响后续任务执行
ScheduledExecutorService 有什么优点
基于线程池,某个任务的执行情况不会影响其他定时任务的执行
线程的生命周期
线程有哪几种生命状态
新建状态( NEW ):创建线程对象
就绪状态( RUNNABLE ):start方法
阻塞状态( BLOCKED ):无法获得锁对象
等待状态( WAITING ):wait方法
计时等待( TIMED_WAITING ):sleep方法
结束状态( TERMINATED ):全部代码运行完毕