计划
多线程
总结
1.线程创建
3种方式
- 继承Thread
- 实现Runnable
- 实现Callable
特点:
3可以有返回值,1,2无返回值
1因为单继承,无法继承其他类
方法:
设置/获取线程名,获取当前线程名,出让,插入,睡眠等
2.同步安全问题
原因
- 大多操作系统对线程采用抢占式调度
- 线程之间会争夺CPU执行权
- 多个线程对同一资源进行操作的过程中,CUP执行权随时会被夺走,被其他线程占用,从而造成安全问题
解决方案
- 对操作共享资源的代码采用锁,只有线程拿到锁,才能进入,执行代码,操作相应资源。未拿到锁的线程即使拿到CPU执行权也无法进入。
- 同步代码块
- 对一段代码设置锁,锁对象可以自己指定
- 代码执行完锁会自动释放
- 同步方法
- 非静态方法以this作为锁对象
- 静态方法以类名.class做为锁对象
- Lock锁
- 需要手动设置锁与释放锁
- 为确保锁被释放,可将释放语句写进try的finally中
注意:
- CPU执行权的抢夺与锁的抢夺是独立的
- 线程只有拿到CPU执行权才可以去抢夺锁
- 线程即使抢到锁,也随时可能被夺走CPU执行权,但因为锁未释放,其他线程无法进入同步代码块,只有等下次锁被释放
- 避免锁的嵌套,防止出现死锁,即外层锁与内层锁被不同线程拿到,全部陷入阻塞
3.等待唤醒机制
流程
- 生产者产出资源,消费者消耗资源
- 生产者
- 有资源:等待
- 无资源
- 生产资源
- 唤醒正在等待的线程(消费者)
- 消费者
- 无资源:等待
- 有资源:
- 消耗资源
- 唤醒正在等待的进程(生产者)
细节
- 等待与唤醒都需要通过同个锁对象(任意类),唤醒的时候才能唤醒所有通过该锁对象进入等待的线程
- 进程进入等待时,释放锁,并交出CPU执行权。在被唤醒时,继续抢夺锁,拿到锁后继续执行后续语句。
实现
- 自己用同步代码块synchronized实现,对操作共享资源的代码设置锁
- 可以使用阻塞队列,阻塞队列内时线程安全的。内部也实现了等待与唤醒。没一个放入与取出操作时一定会成功执行的,无法放入进入等待,下次唤醒会继续放入。
4.线程池
意义
- 降低资源消耗:复用已创建的线程,避免频繁创建和销毁线程(
new Thread+start())带来的性能开销 - 提高响应速度:任务到达时,无需等待线程创建,可直接使用空闲线程立即执行
- 提高线程可管理性:线程是稀缺资源,统一分配、调优和监控,防止无限制创建导致系统崩溃(OOM)
核心参数(ThreadPoolExecutor 的7个参数)
- 核心线程数(corePoolSize):线程池中一直存活的线程数,即使空闲也不会被回收(除非设置了
allowCoreThreadTimeOut) - 最大线程数(maximumPoolSize):线程池中允许的最大线程数
- 存活时间(keepAliveTime):非核心线程空闲超过该时间会被回收
- 时间单位(unit):
keepAliveTime的时间单位(如TimeUnit.SECONDS) - 阻塞队列(workQueue):存放等待执行的任务(如
ArrayBlockingQueue、LinkedBlockingQueue) - 线程工厂(threadFactory):创建线程的工厂,可自定义线程名、优先级等(默认
Executors.defaultThreadFactory()) - 拒绝策略(handler):当线程池和队列都满时,对新提交的任务的处理方式
执行流程(任务提交后的流转)
- 核心线程:当前线程数 < 核心线程数 → 创建新线程执行任务
- 阻塞队列:核心线程已满 → 任务放入队列等待
- 最大线程:队列已满 → 创建非核心线程执行任务(直到达到最大线程数)
- 拒绝策略:最大线程也满了 → 执行拒绝策略
注意:流程是 核心 → 队列 → 最大,而非很多人误以为的核心 → 最大 → 队列
拒绝策略(4种内置实现)
AbortPolicy(默认):直接抛出RejectedExecutionException,阻止系统正常运行CallerRunsPolicy:由调用者线程(提交任务的线程)自己执行该任务,提供反馈机制,减缓任务提交速度DiscardPolicy:直接丢弃任务,不抛出异常(丢失任务)DiscardOldestPolicy:丢弃队列中最旧的任务,然后重试提交当前任务
提交任务的两种方式
execute(Runnable command)- 提交无返回值的任务
- 异常直接抛出(控制台打印),调用者无法捕获
submit(Callable/Runnable task)- 提交有返回值(
Callable)或无返回值(Runnable)的任务 - 返回
Future对象,可通过future.get()获取结果或捕获异常 Runnable提交时,Future.get()返回nullRunnable+ 指定结果:submit(Runnable task, T result),Future.get()返回指定的result
- 提交有返回值(
注意
- 线程池中的线程是复用的:底层
Worker通过while循环不断从队列中取任务执行,执行完一个任务后不会销毁,而是继续取下一个任务 Thread对象与内核线程:Thread对象只是堆内存中的普通对象,真正执行能力来自操作系统内核线程(LWP)。线程池复用的是内核线程,而不是Thread对象本身- 不能直接操作池中某个线程:线程池封装了线程的个体操作,应通过
Future控制任务(如future.cancel(true)中断任务),而非直接操作线程 execute与submit底层关系:submit底层也是调用execute,但会将Runnable/Callable包装成RunnableFuture(即FutureTask),从而支持返回值和异常捕获- 线程池关闭:使用
shutdown()优雅关闭(不再接收新任务,等待已提交任务执行完毕),或shutdownNow()立即停止(尝试中断正在执行的任务,返回未执行的任务列表)