https://blog.csdn.net/ADbyCool/article/details/107457983
并发编程笔记:什么是多线程的上下文切换,有什么影响?如何进行优化
1.什么是CPU时间片?
CPU会给每个线程分配执行的时间,也就是CPU时间片
CPU时间片是一个小的时间单位,一般为几十毫秒
当线程用完分给它的时间片后,系统的计时器发出时钟中断,调度程序便停止该线程的运行,把它放入就绪队列的末尾;然后把CPU分给其他就绪的线程,同样也让它运行一个时间片,如此往复。
2.什么是线程上下文切换?
对于CPU而言,在一个时刻只能运行一个线程,当一个线程的时间片用完,或者因自身原因被迫暂停运行,此时另一个线程会被操作系统选中来占用处理器
当CPU结束运行一个线程,转去执行另外一个线程,这个过程就叫做线程上下文切换
3.线程的上下文具体是什么?
在发生切换的时候,当前线程的任务可能并没有执行完毕。所以在切换时需要保存线程切换前的运行状态,以便下一次,可以接着切换之前的状态继续执行后续的任务
切出切入的过程中,操作系统需要保存和恢复相应的进度信息,这个进度信息就是上下文
4.看一个线程上下文切换的具体例子?
比如一个线程A正在读取一个文件的内容,正读到文件的一半,线程A的时间片结束,此时需要暂停线程A,CPU转去执行线程B
当再次切换回来执行线程A的时候,我们不希望线程A又从文件的开头来读取
5. 上下文的内容一般都有哪些?
一般来说,线程上下文切换过程中会涉及程序计数器、CPU寄存器:
6.什么是CPU寄存器?
寄存器的存储内容:CPU寄存器负责存储已经、正在和将要执行的任务
在线程正在进行某个计算的时候被挂起了,那么下次继续执行的时候需要知道之前挂起时变量的值时多少
7.什么是程序计数器?
程序计数器存储的指令内容:程序计数器负责存储CPU正在执行的指令位置、即将执行的下一条指令的位置
线程在进行切换时候,需要知道在这之前当前线程已经执行到哪条指令了,这些指令信息需要依靠程序计数器来保存
程序计数器是一块较小的内存空间
它保存了当前线程下一条需要执行的字节码指令的位置
每条线程都需要有一个独立的程序计数器
线程之间的程序计数器互不影响
堆是共享的
而程序计数器一定是线程私有的
8.线程上下文切换为什么会消耗系统资源?
Java的线程底层是映射到操作系统原生线程之上的
如果要阻塞或唤醒一个线程就需要操作系统介入
需要在用户态与内核态之间切换
这种切换会消耗大量的系统资源
9.什么是用户态?
在执行用户自己的代码时,称其处于用户态
此时处理器特权级别最低
是普通的用户进程运行的特权级
大部分用户直接面对的程序都是运行在用户态
10.什么是内核态?
当因为系统调用陷入内核代码中执行时处于内核态
此时处理器处于特权级最高
11.用户态和内核态切换需要传递和保存哪些东西?
如果要执行文件操作、网络数据发送等操作必须通过write、send等系统调用
此时需要从用户态切换到内核态。
这些系统调用会调用内核的代码
在执行完后又会切换回用户态
用户态与内核态都有各自专用内存空间和专用寄存器等
用户态切换至内核态需要传递给许多变量、参数给内核
内核也需要保护好用户态在切换时的一些寄存器值、变量等,以便内核态调用结束后切换回用户态继续工作
12.线程上下文切换一般分为哪两种?
第一种:自发性上下文切换
第二种:非自发性上下文切换
13.自发性上下文切换一般使用哪些方法?
sleep()
wait()
yield()
join()
park()即真正让线程停止下来(阻塞),Java提供了一个较为底层的并发工具类:LockSupport,该类常用的方法有两个
1.park(Object blocker) 表示阻塞指定线程,参数blocker当前线程对象
2 unpark(Thread thread) 唤醒指定线程,参数thread指定线程对象
synchronized
lock
14.哪些是非自发性上下文切换?
线程被分配的时间片用完
JVM垃圾回收(STW、线程暂停)
线程执行优先级
15.线程上下文切换带来的系统开销如何优化?
线程上下文切换带来的系统消耗不可避免
核心思想:减少线程上下文切换的出现
优化一:减少锁的竞争
优化二:减少锁的持有时间
优化三:减少锁的粒度中的锁读写分离
优化四:减少锁的粒度中的锁分段
优化五:用非阻塞乐观锁代替竞争锁
优化六:减少GC频率
16.减少上下文切换为什么要减少锁的竞争?
多线程对锁资源的竞争,如果失败由于进入阻塞状态,将会引起上下文切换
锁竞争导致的线程阻塞越多
锁竞争带来很多次的上下文切换
系统的性能开销就越大
在多线程编程中,锁本身不是性能开销的根源
锁竞争才是性能开销的根源
锁优化归根到底是减少竞争
17.减少上下文切换为什么要减少锁的持有时间?
锁的持有时间越长,意味着越多的线程会被阻塞,在等待该竞争锁释放
优化方法:将一些与锁无关的代码移出同步代码块,尤其是那些开销较大的操作以及可能被阻塞的操作
18.减少上下文切换为什么要减少锁粒度中的锁读写分离?
读写锁实现了锁分离
由读锁和写锁两个锁实现,
可以共享读,但只有一个写
读写锁在多线程读写时,
读读不互斥
读写互斥,写写互斥
传统的独占锁在多线程读写时
读读互斥,读写互斥,写写互斥
在读远大于写的多线程场景中,锁分离避免了高并发读情况下的资源竞争,从而避免了上下文切换
19.减少上下文切换为什么要减少锁的粒度中的锁分段?
在使用锁来保证集合或者大对象的原子性时
可以将锁对象进一步分解
Java 1.8之前的ConcurrentHashMap就是用了锁分段
20.减少上下文切换为什么可以用非阻塞乐观锁代替竞争锁?
CAS是一个原子的if-then-act操作
CAS是一个无锁算法实现
保障了对一个共享变量读写操作的一致性
CAS不会导致上下文切换,Java的Atomic包就使用了CAS算法来更新数据,而不需要额外加锁
21.减少上下文切换为什么要减少GC频率?
很多垃圾回收器在回收旧对象时会产生内存碎片
从而需要进行内存整理
该过程需要移动存活的对象
而移动存活的对象意味着这些对象的内存地址会发生改变
因此在移动对象之前需要暂停线程
完成后再唤醒线程
因此减少GC的频率能够有效的减少上下文切换