进程是 操作系统
的管理单位,而线程则是 进程
的管理单位;一个进程至少包含一个执行线程。不管是在单线程还是多线程中,每个线程都有一个 程序计数器
(记录要执行的下一条指令),一组 寄存器
(保存当前线程的工作变量),堆栈
(记录执行历史,其中每一帧保存了一个已经调用但未返回的过程)。虽然线程寄生在进程 中,但与进程是不同的概念,并且可以分别处理:进程是系统分配资源的基本单位,线程是调度 CPU 的基本单位。
一个线程指的是进程中一个单一顺序的控制流,一个进程中可以并行多个线程,每条线程并行执行不同的任务。每个线程共享堆空间,但拥有自己独立的栈空间。
进程之间相互独立,通信比较困难,而线程之间共享一块内存区域,通信方便。
操作系统的设计者巧妙地利用了时间片轮转的方式,CPU 给每个任务都服务一定的时间,然后把当前任务的状态保存下来,在加载下一任务的状态后,继续服务下一任务。任务的状态保存及再加载, 这段过程就叫做上下文切换。时间片轮转的方式使多个任务在同一个 CPU 上执行变成了可能。
上下文切换(有时也称做进程切换或任务切换)是指 CPU 从一个进程或线程切换到另一个进程或线程。
引起线程上下文切换的原因,主要存在以下几种情况:
1,当前执行任务的时间片用完之后,系统 CPU 正常调度下一个任务;
2,当前执行任务碰到 IO 阻塞,调度器将此任务挂起,继续下一任务;
3,多个任务抢占锁资源,当前任务没有抢到锁资源,被调度器挂起,继续下一任务;
4,用户代码挂起当前任务,让出 CPU 时间;
5,中断处理:在中断处理中,其他程序 ”打断” 了当前正在运行的程序。当 CPU 接收到中断请求时,会在正在运行的程序和发起中断请求的程序之间进行一次上下文切换。中断分为硬件中断和软件中断,软件中断包括因为 IO 阻塞、未抢到资源或者用户代码等原因,线程被挂起。
减少上下文切换的方法有 无锁并发编程、CAS 算法、使用最少线程和使用协程。
1)无锁并发:多线程竞争时,会引起上下文切换,所以多线程处理数据时,可以用一些办法来避免使用锁,如将数据的 ID 按照 Hash 取模分段,不同的线程处理不同段的数据;
2)CAS 算法:Java 的 Atomic 包使用 CAS 算法来更新数据,而不需要加锁;
3)最少线程:避免创建不需要的线程,比如任务很少,但是创建了很多线程来处理,这样会造成大量线程都处于等待状态;
4)使用协程:在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换;
线程数应该设置多少合理呢?
遵循以下几个原则:
对于任务耗时短的情况,要求线程尽量少;
对于耗时长的任务,要分是 CPU 任务,还是 IO 等类型的任务;
如果是 CPU 类型的任务,线程数不宜太多;但是如果是 IO 类型的任务,线程多一些更好,可以更充分利用 CPU;
- 高并发,低耗时的情况:建议少线程,只要满足并发即可;
高并发就意味着 CPU 处于繁忙状态的, 增加更多地线程也不会让线程得到执行时间片,反而会增加线程切换的开销;例如并发100,线程池可能设置为10就可以。
低并发,高耗时的情况:建议多线程,保证有空闲线程,接受新的任务;例如并发10,线程池可能就要设置为20;
高并发高耗时:1. 要分析任务类型;2. 增加排队;3. 加大线程数;