对于后端开发来说,上下文我们常接触.
那什么是线程的上下文切换?
1.线程的上下文切换
在基础篇(一)中,我们讲到了时间片的概念.在单核处理器中,CPU就是通过给每个线程分配时间片执行来实现多线程.时间片是CPU分配给每个线程的执行时间段,这段时间都很短,只有几毫秒(ms),所以CPU必须来回切换各个线程来执行各自的任务,这样,对外看来,是在同时执行多个任务.
可能让我们疑惑的是,CPU如何保证下次再执行某个线程时,能正确记住该线程执行任务的状态?这就是我们要说的线程的上下文切换.CPU通过时间片分配算法来实现多任务,当前任务执行完对应的时间片后,需要切换到下一个任务.但是,在切换到下一个任务之前,需要保存上一个任务的状态,以便下次切换到该任务时,能正确的加载该任务的状态.这个过程就叫做线程的上下文切换.
举个例子,我记得上小学时候,老师讲课中间,其他老师会半途过来借东西,然后老师需要停下讲课内容,然后把东西借出去,然后说一句:我们继续.在这个过程中,老师必须要在脑子中记忆刚到的地方,不至于被其他事情打断后重来.这个过程就是上一件事情和下一件事情之间的切换.
因为有其他任务/事情插入,所以我们正在做的任务/事情会被中断,影响效率.因此,多线程任务的上下文切换是会影响执行效率的.
2.多线程执行与串行执行效率比较
多线程执行一定快吗?答案是不一定.我们模拟下面的执行过程.
public class CurrentSpeedTestDemo { private static final long count = 10000l; public static void main(String[] args) throws InterruptedException { concurrencyCount(); serialCount(); } private static void concurrencyCount() throws InterruptedException { long start = System.currentTimeMillis(); final Thread thread = new Thread(new Runnable() { @Override public void run() { int b = 0; for (long i = 0; i < count; i++) { b += 5; } System.out.println("并行执行得到:b=" + b); } }); thread.start(); long time = System.currentTimeMillis() - start; System.out.println("并行执行花费时间:" + time+"ms"); } private static void serialCount() { long start = System.currentTimeMillis(); int b = 0; for (long i = 0; i < count; i++) { b += 5; } long time = System.currentTimeMillis() - start; System.out.println("串行执行得到:b=" + b); System.out.println("串行执行花费时间:" + time+"ms"); } }
执行效果如下:(这是我的电脑执行效果)
从执行测试效果可以看出,当累加操作在1万次时,并行是慢于串行执行的.
3.减少上下文切换
减少上下文切换的方法有多种,常用的有 无锁并发编程,CAS,使用最少线程,使用协程.
①无锁并发编程:在编程中,首先考虑无锁实现.
②CAS:比较交换,在Java的原子类实现中,使用CAS来做数据更新.
③使用最少线程:在并发很小的情况下,尽量不使用大量线程.
④使用协程:协程可以实现在单线程的情况下多任务的切换.