一、上下文切换
1、概念:
(这边解释的是线程的上下文切换,而不是进程的上下文切换)
在某种程度上可以看成多个线程共享同一个处理器的产物。即多线程中,一个线程被暂停(剥夺)处理器的使用权,另外一个线程被选中开始或者继续运行的过程。
解释:单处理器可以执行多线程,是因为多个线程被一个处理器以不同的时间处理(时间片轮转,极小时间使得人感受不到,以为多个任务一同进行)。比如两个线程AB,当A线程由于其时间片用完或者自身的原因停止转向另一个线程B,B线程可以被操作系统宣红占用处理器运行的过程叫上下文切换。
- 线程被剥夺处理器的使用权并且暂停的过程叫切出(switch out)。
- 一个线程被OS选中占用处理器开始运行的过程叫切入(switch in)。
- 切入切出的时候,信息需要保存和恢复,这些进度信息就是叫上下文(寄存器的内容和计数器的内容)。线程在非RUNNABLE和RUNNABLE的情况下切换的过程就是上下文切换。
2、分类和诱因:
导致上下文切换的原因粉自发性上下问切换和非自发性上下问切换。
自发性:是指由于线程自身原因的切出。比如I/O操作,等待线程持有锁或者以下:
非自发性:是指线程由于线程调度器的原因被迫切出,比如:切出线程的时间片用完,或者比当前线程更高级的线程需要被运行,或者Java虚拟机的垃圾回收(回收的时候可能需要暂停所有应用线程)。
3、上下文切换和开销:
上下文切换的时候需要直接开销或者间接开销:所以并不是线程越多计算效率越高的。
那么如何保证一个多线程在某个时间段或者场景下运行上下文切换的次数:
Linux:他的内核直接提供perf命令来监视。
Windows:自带的perfmon来监视。
二、线程的活性故障
由资源稀缺性或者程序自身的问题和缺陷导致线程一直处于非Runnable状态,或者线程虽然处于Runnable状态但是其要执行的任务却一直无法进展的故障现象。
常见的活性故障包括以下几种:
- 死锁:
产生多种条件下,线程进行僵持
a、产生条件(必要非充分)
资源互斥
资源不可抢夺
占用并等待资源
循环等待资源
b、破坏任一条件即可避免死锁
一般从“占用并等待资源”和“循环等待资源”两方面破坏条件
比如:
粗锁法:使用粗粒度的锁待敌代替多个锁(破坏 占用并等待资源)
缺点:降低了并发性并可能导致资源浪费
锁排序法:相关线程使用全局统一的顺序申请锁(破坏 循环等待资源)
使用ReentrantLock.tryLock(long,TimeUnit)申请锁,指定了一个超时时间,在时间内锁申请成功则返回true,超时则返回false
- 锁死:等待外部叫醒,但是外部条件没有成立
a、信号丢失锁死
由于没有相应的通知线程来唤醒等待线程而使等待线程一直处于等待状态的一种活性故障
b、嵌套监视器锁死
嵌套锁导致等待线程永远无法被唤醒的一种活性故障
- 活锁:线程一直处于RUNNABLE但是任务却没有执行
- 饥饿:线程总是无法获得资源而使得任务无法执行
3、资源的征用和调度
由于资源的缺少或者其自身特性,多个线程共享一个资源的时候,一次只能由一个线程来占用资源(有些资源具有排他性:处理器、数据库连接、文件等)。这时候会发生资源的争用(有高争用和低争用)。
(注意:处于运行状态的线程越多,并发越高。但是高并发并不一定是高争用,这要看提供的资源量。我们理想是高并发,低争用)。
公平调度策略(线程占用时间长或者资源平均申请时间间隔长):
按照申请的先后顺序进行授予资源的独占权。他避免了饥饿现象出现。只有当等待队列为空时,资源申请者可以进行抢占,如果失败就进入等待队列。
公平调度策略的优缺点:
优点:适合在资源的持有线程占用资源的时间相对长或者资源的平均申请时间间隔相对长的情况下,或者对资源申请所需的时间偏差有所要求的情况下使用;线程申请资源所需的时间偏差较小;不会出现线程饥饿的现象
缺点:吞吐率较小
非公平调度策略(多数情况下选择):
没有按照先后顺序授予资源的独占权。
非公平调度的解释:在该策略中,资源的持有线程释放该资源的时候,等待队列中一个线程会被唤醒,而该线程从被唤醒到其继续执行可能需要一段时间。在该时间内,新来的线程(活跃线程)可以先被授予该资源的独占权。
如果新来的线程占用该资源的时间不长,那么它完全有可能在被唤醒的线程继续执行前释放相应的资源,从而不影响该被唤醒的线程申请资源。被唤醒的线程不一定能够抢占在资源,如果一直如此会发生饥饿现象。
非公平调度策略优缺点:
优点:吞吐率较高,即单位时间内可以为更多的申请者调配资源;如果新线程先抢到资源,但是占用时间少,那么在其他线程被唤醒前完成,就可以减少上下文切换。但是如果占用时间长,那么就会增加上下文切换。
缺点:资源申请者申请资源所需的时间偏差可能较大,并可能出现线程饥饿的现象。