进程被分为后台进程和应用进程
大部分后台进程在系统开始运行时被操作系统启动,完成操作系统的基础服务功能。大部分应用进程由用户启动,完成用户所需的具体应用功能
进程由程序段、数据段、进程控制块三部分组成
程序段也被称为是代码段,代码段是进程的操作数据在内存中的位置,包括需要操作的数据集合;程序控制块包含进程的描述信息和控制信息,是进程的存在的唯一标识
程序控制块(Program Control Block,PCB)包含进程的描述信息和控制信息,是进程存在的唯一标 志。
PCB分为四部分
- 进程的描述信息
- 进程ID,进程名称,进程ID是唯一的,进程状态,进程优先级
- 进程的调度信息
- 程序起始地址
- 通信信息
- 进程的资源信息
- 内存信息,内存占用情况和内存管理所用的数据结构
- I/O设备信息
- 文件句柄
- 进程的上下文
- 执行时各种CPU寄存器的值
- 当前线程计数器
- 各种栈值信息
线程由三部分组成
- 线程的描述信息
- 线程ID
- 线程名称
- 线程优先级
- 线程状态
- 其他,例如是否为守护线程
- 程序计数器
- 栈内存
通过实现Runnable接口的方式创建线程目标类
缺点:
- 所创建的类并不是线程类,而是线程的执行目标类
- 如果访问当前线程的属性,不能直接访问Thread的实例方法,必须通过Thread.currentThread()获取当前的线程实例,才能访问和控制当前线程。
优点:
- 避免Java单继承的局限性,如果异步逻辑所在的类已经继承了一个基类,就没办法继承Thread类
- 逻辑和数据更好的分离
继承Thread类实现多线程能更好地做到多个线程并发的完成各自的任务,访问各自的数据
使用Callable和FurtureTask创建线程
RunnableFurture接口
Future接口提供了三大功能:
- 能够取消异步执行的任务
- 判断异步任务是否执行完成
- 获取异步任务完成后的结果
通过线程池创建执行目标提交
private static ExecutorService pool =
Executors.newFixedThreadPool(3);
线程的调度模型
-
分时调度模型
系统平均分配CPU时间片
-
抢占式调度模型
优先级高的优先分配
线程的生命周期
public static enum State {
NEW, //新建
RUNNABLE, //可执行:包含操作系统的就绪、运
行两种状态
BLOCKED, //阻塞
WAITING, //等待
TIMED_WAITING, //限时等待
TERMINATED; //终止
}
四种常见状态
-
NEW状态
创建成功但是没有调用start()方 法启动的Thread线程实例都处于NEW状态。
-
RUNNABLE状态
当Java 线程的Thread实例的start()方法被调用后,操作系统中的对应线程进 入的并不是运行状态,而是就绪状态,而Java线程并没有这个就绪状 态。
一旦就绪状态被系统选中,获得CPU时 间片,线程就开始占用CPU,开始执行线程的代码,这时线程的操作系 统状态发生了改变,进入了运行状态。
-
TERMINATED状态
处于RUNNABLE状态的线程在run()方法执行完成之后就变成终止状 态TERMINATED了。
-
TIMED_WAITING状态
线程处于一种特殊的等待状态,准确地说,线程处于限时等待状态。
- Thread.sleep(int n):使得当前线程进入限时等待状态, 等待时间为n毫秒。
- Object.wait():带时限的抢占对象的monitor锁
- Thread.join():带时限的线程合并。
- LockSupport.parkNanos():让线程等待,时间以纳秒为单 位。
- LockSupport.parkUntil():让线程等待,时间可以灵活设置
-
yield仅能使一个线程从运行状态变为就绪状态,而不是阻塞状态
-
yield不能保证使得当前正在运行的线程迅速转换到就绪状 态。
-
即使完成了迅速切换,系统通过线程调度机制从所有就绪线 程中挑选下一个执行线程时,就绪的线程有可能被选中,也有可能不 被选中,其调度的过程受到其他因素(如优先级)的影响
守护线程和用户线程
守护线程在JVM中相当于保姆的角色:只 要JVM实例中尚存在任何一个用户线程没有结束,守护线程就能执行自 己的工作;只有当最后一个用户线程结束,守护线程随着JVM一同结束 工作。
守护线程和用户线程的本质区别是:二者与JVM虚拟机进 程终止的方向不同。用户线程和JVM进程是主动关系,如果用户线程全 部终止,JVM虚拟机进程也随之终止;守护线程和JVM进程是被动关 系,如果JVM进程终止,所有的守护线程也随之终止
使用守护线程需要注意的方面:
- 守护线程必须在启动前将其守护状态设置为true,启动之后 不能再将用户线程设置为守护线程,否则JVM会抛出一个 InterruptedException异常。
- 守护线程存在被JVM强行终止的风险,所以在守护线程中尽 量不去访问系统资源,如文件句柄、数据库连接等。守护线程被强行 终止时,可能会引发系统资源操作不负责任的中断,从而导致资源不 可逆的损坏。
- 守护线程创建的线程也是守护线程。
线程进入就绪状态的条件:
- 调用线程的start()方法,此线程就会进入就绪状态。
- 当前线程的时间片用完
- 线程睡眠(Sleep)操作结束
- 对其他线程合入(Join)操作结束
- 等待用户输入结束
- 线程争抢到对象锁(Object Monitor)
- 当前线程调用了yield()方法让出CPU执行权限
处于阻塞状态的线程不会占用CPU的资源
进入阻塞的情况
- 线程等待获取锁
- IO阻塞
线程的创建
- 必须为线程堆栈分配和初始化大量内存块,其中包含至少 1MB的栈内存
- 需要进行系统调用,以便在OS(操作系统)中创建和注册本 地线程
线程池解决的问题:
- 提升性能:线程池能独立负责线程的创建、维护和分配。在 执行大量异步任务时,可以不需要自己创建线程,而是将任务交给线程池去调度。线程池能尽可能使用空闲的线程去执行异步任务,最大 限度地对已经创建的线程进行复用,使得性能提升明显。
- 线程管理:每个Java线程池会保持一些基本的线程统计信 息,例如完成的任务数量、空闲时间等,以便对线程进行有效管理, 使得能对所接收到的异步任务进行高效调度。
由于创建和销毁线程需要时间以及系统资源开销,使用线程池的好处是减少这些开销,解决资源不足的问题
JUC的线程架构
-
Executor
Executor是Java异步目标任务的执行者接口,其目标是执行目标任务,提供了execute()接口来执行已提交的Runnable执行目标实例。
-
ExecutorService
继承于Executor。它是Java异步目标任务的执行者服务接口,对外提供异步任务的接收服务。
-
AbstractExecutorService
是一个抽象类,它实现了ExecutorService接口,存在的目的是为了ExecutorService中的接口默认实现
-
ThreadPoolExecutor
线程池的实现类,继承于AbstractExecutorService抽象类
JUC线程池的核心实现类 提供了指定数量的可重用线程,所以使用线程需要很大的开销
-
ScheduleExcutorService
是一个接口可以完成延时和周期性任务的调度线程池的接口。其功能和Timer/TimerTask类似
-
ScheduledThreadPoolExecutor
-
Executors
静态工厂类,通过静态工厂方法返回线程池的实例对象
单线程化的线程池中的任务是按照提交次序顺序执行的
池中唯一线程的存活时间是无限的
当池中唯一线程正繁忙时,新提交的任务实例会进入内部的阻塞队列中,并且其阻塞队列是无界的
用例最后调用shutdown()方法来关闭线程池。
newFixedThreadPool创建“固定数量的线程池”
- 如果固定数量线程池没有达到“固定数量”,每次提交一个任务线程池就创建一个新的线程,直到线程达到线程池的固定数量。
- 线程池的大小一旦达到“固定数量”就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新的线程
- 在接受异步任务的执行目标实例时,如果池中所有的线程均处于繁忙状态,新任务会进入阻塞队列中(无界的阻塞队列)
固定数量线程池适用场景;需要任务长期执行的场景。固定数量的线程池的线程数量能比较稳定的保证一个数,避免频繁的回收线程和创建线程,固适用于处理CPU密集型任务,在CPU被工作线程长时间占用时,能确保尽可能少的分配线程
弊端:
- 内部使用无界队列来存放排队任 务,当大量任务超过线程池最大容量需要处理时,队列无限增大,使 服务器资源迅速耗尽。
newCachedThreadPool创建“可缓存线程池”
任务被拒绝有两种情况:
- 线程池已经被关闭
- 工作队列已满且maxmumPoolSize已满
线程池的拒绝策略
-
AbortPolicy:拒绝策略(线程池默认的策略)
使用该策略时,如果线程池队列满了,新任务就会被拒绝,并且 抛出RejectedExecutionException异常。该策略是线程池默认的拒绝 策略。
-
DiscardPolicy:抛弃策略
该策略是AbortPolicy的Silent(安静)版本,如果线程池队列满 了,新任务就会直接被丢掉,并且不会有任何异常抛出。
-
DiscardOldestPolicy:抛弃最老任务策略
抛弃最老任务策略,也就是说如果队列满了,就会将最早进入队 列的任务抛弃,从队列中腾出空间,再尝试加入队列。因为队列是队 尾进队头出,队头元素是最老的,所以每次都是移除队头元素后再尝 试入队。
-
CallerRunsPolicy:调用者执行策略
调用者执行策略。在新任务被添加到线程池时,如果添加失败, 那么提交任务线程会自己去执行该任务,不会使用线程池中的线程去 执行新任务。
-
自定义策略
如果以上拒绝策略都不符合需求,那么可自定义一个拒绝策略, 实现RejectedExecutionHandler接口的rejectedExecution方法即可。
线程池的五种状态
- RUNNING:线程池创建之后的初始状态,这种状态下可以执行任务
- SHUTDOWN:该状态下线程池不再接受新任务,但是会将工作队列中的任务执行完毕
- STOP:该状态下线程池不再接受新任务,也不会处理工作队列的剩余任务,并且将会中断所有工作线程。
- TIDYING:该状态下所有任务都已终止或者处理完成,将会 执行terminated()钩子方法。
- TERMINATED:执行完terminated()钩子方法之后的状态
线程池的转换规则:
- 线程池创建之后状态为RUNNING。
- 执行线程池的shutdown()实例方法,会使线程池状态从 RUNNING转变为SHUTDOWN。
- 执行线程池的shutdownNow()实例方法,会使线程池状态从 RUNNING转变为STOP。
- 当线程池处于SHUTDOWN状态时,执行其shutdownNow()方法 会将其状态转变为STOP。
- 执行完terminated()钩子方法之后,线程池状态从TIDYING 转变为TERMINATED。
优雅的关闭线程池主要涉及的方法有三个:
- shutdown:是JUC提供的一个有序关闭线程池的方法,此方 法会等待当前工作队列中的剩余任务全部执行完成之后,才会执行关 闭,但是此方法被调用之后线程池的状态转为SHUTDOWN,线程池不会 再接收新的任务
- shutdownNow:是JUC提供的一个立即关闭线程池的方法,此 方法会打断正在执行的工作线程,并且会清空当前工作队列中的剩余 任务,返回的是尚未执行的任务。
- awaitTermination:等待线程池完成关闭。在调用线程池的 shutdown()与shutdownNow()方法时,当前线程会立即返回,不会一直 等待直到线程池完成关闭。如果需要等到线程池关闭完成,可以调用 awaitTermination()方法。
使用Executors快捷创建线程池的潜在问题
-
创建固定数量线程池的潜在问题
主要存在于其workQueue上,其值为LinkedBlockingQueue(无 界阻塞队列)。如 果任务提交速度持续大于任务处理速度,就会造成队列中大量的任务 等待。如果队列很大,很有可能导致JVM出现OOM(Out Of Memory)异 常,即内存资源耗尽。
-
使用Executors创建“单线程化线程池”的潜在问题
潜在问题仍然存在于其workQueue属性上,该属性的值为 LinkedBlockingQueue(无界阻塞队列)。如果任务提交速度持续大于 任务处理速度,就会造成队列大量阻塞。如果队列很大,很有可能导 致JVM的OOM异常,甚至造成内存资源耗尽。
-
使用Executors创建“可缓存线程池”的潜在问题
(1)FixedThreadPool和SingleThreadPool 这两个工厂方法所创建的线程池,工作队列(任务排队的队列) 的长度都为Integer.MAX_VALUE,可能会堆积大量的任务,从而导致 OOM(即耗尽内存资源)。
(2)CachedThreadPool和ScheduledThreadPool 这两个工厂方法所创建的线程池允许创建的线程数量为 Integer.MAX_VALUE,可能会导致创建大量的线程,从而导致OOM。
线程池的好处:
- 降低资源消耗
- 提高响应速度
- 提高线程的可管理性