Java多线程--常用概念
2021-05-06 本文已影响0人
轻轻敲醒沉睡的心灵
1. 进程和线程
在计算机中,我们把一个任务称为一个进程,浏览器就是一个进程,视频播放器是另一个进程,类似的,音乐播放器和Word都是进程。
某些进程内部还需要同时执行多个子任务。例如,我们在使用Word时,Word可以让我们一边打字,一边进行拼写检查,同时还可以在后台进行打印,我们把子任务称为线程。
进程和线程的关系就是:一个进程可以包含一个或多个线程,但至少会有一个线程。
操作系统调度的最小任务单位其实不是进程,而是线程。常用的Windows、Linux等操作系统都采用抢占式多任务,如何调度线程完全由操作系统决定,程序自己不能决定什么时候执行,以及执行多长时间。
因为同一个应用程序,既可以有多个进程,也可以有多个线程,因此,实现多任务的方法,有以下几种:
-
多进程模式(每个进程只有一个线程)
多进程模式 -
多线程模式(一个进程有多个线程)
多线程模式 -
多进程+多线程模式(复杂度最高)
多进程+多线程模式
和多线程相比,多进程的缺点在于:
- 创建进程比创建线程开销大,尤其是在Windows系统上;
- 进程间通信比线程间通信要慢,因为线程间通信就是读写同一个变量,速度很快。
多进程的优点在于:
- 多进程稳定性比多线程高,因为在多进程的情况下,一个进程崩溃不会影响其他进程,而在多线程的情况下,任何一个线程崩溃会直接导致整个进程崩溃。
2. 上下文
上下文是指某一时间点CPU寄存器和程序计数器的内容。
- 寄存器
寄存器是CPU内部的数量较少但是速度很快的内存(与之对应的是CPU外部相对较慢的RAM主内存)。 寄存器通过对常用数据(通常是运算的中间值)的快速访问来提高计算机程序运行的速度。 - 程序计数器
程序计数器是一个专用的寄存器,用于表明指令序列中CPU正在执行的位置,存的值为正在执行的指令的位置或者下一个将要被执行的指令的位置,和操作系统相关。
3. 上下文切换
巧妙地利用了时间片轮转的方式, CPU 给每个任务都服务一定的时间,然后把当前任务的状态保存下来,在加载下一个任务的状态后,继续服务下一个任务,任务的状态保存及再加载, 这段过程就叫做上下文切换。 时间片轮转的方式使多个任务在同一颗 CPU 上执行变成了可能。
上下文切换可以认为是内核(操作系统的核心)在CPU对于进程(包括线程)进行切换,上下文切换过程中的信息是保存在进程控制块(PCB,
Process Control Block
)中的。 PCB还经常被称作“切换桢”(switch frame
)。信息会一直保存到CPU的内存中,直到他们被再次使用。
- 上下文切换步骤
- 挂起一个进程,将这个进程在 CPU 中的状态(上下文)存储于内存中的某处。
- 在内存中检索下一个进程的上下文并将其在 CPU 的寄存器中恢复。
- 跳转到程序计数器所指向的位置(即跳转到进程被中断时的代码行),以恢复该进程在程序中。
- 上下文切换的原因
- 当前执行任务的时间片用完之后,系统 CPU 正常调度下一个任务;
- 当前执行任务碰到 IO 阻塞,调度器将此任务挂起,继续下一个任务;
- 多个任务抢占锁资源,当前任务没有抢到锁资源,被调度器挂起,继续下一个任务;
- 用户代码挂起当前任务,让出 CPU 时间;
- 硬件中断;
- 上下文切换问题
上下文切换会导致额外的开销,常常表现为高并发执行时速度会慢串行,因此减少上下文切换次数便可以提高多线程程序的运行效率。- 直接消耗:指的是CPU寄存器需要保存和加载, 系统调度器的代码需要执行, TLB实例需要重新加载, CPU 的pipeline需要刷掉
- 间接消耗:指的是多核的cache之间得共享数据, 间接消耗对于程序的影响要看线程工作区操作数据的大小
- 优化手段
- 无锁并发编程,多线程处理数据时,可以用一些办法来避免使用锁,如将数据的ID按照Hash取模分段,不同的线程处理不同段的数据
- CAS算法,Java的Atomic包使用CAS算法来更新数据,而不需要加锁
- 使用最少线程
- 协程,单线程里实现多任务的调度,并在单线程里维持多个任务间的切换