java 线程上下文_【并发编程】线程上下文切换

1.1 概念

单核处理器也支持多线程执行代码,CPU通过给每个线程分配CPU时间片来实现这个机制。(时间片是CPU分配给各个线程的时间,因为时间片非常短,一般是几十毫秒,所以CPU通过不停地切换线程执行,让我们感觉多个线程是同时执行的)。

CPU通过时间片分配算法来循环执行任务,当前任务执行一个时间片后会切换到下一个任务。但是,在切换前会保存上一个任务的状态,以便下次切换回这个任务时,可以再加载这个任务的状态。所以任务从保存到再加载的过程就是一次上下文切换(Context Switch)。

97528ea159ed3cb0cadd3e9b31f69990.png

可见,线程上下文切换的过程,就是一个线程被暂停剥夺使用权,另一个线程被选中开始或者继续运行的过程。

1.2 案例说明public class ContextSwitchTest {

private static final long count = 10000;

public static void main(String[] args) throws Exception {

serial();

concurrency();

}

// 串行

private static void serial() {

long start = System.currentTimeMillis();

int a = 0;

for (long i = 0; i < count; i++) {

a += 5;

}

int b = 0;

for (int i = 0; i < count; i++) {

b--;

}

long time = System.currentTimeMillis() - start;

System.out.println("Serial:" + time + "ms, b = " + b + ", a = " + a);

}

// 并发

private static void concurrency() throws Exception {

long start = System.currentTimeMillis();

Thread thread = new Thread(new Runnable() {

public void run() {

int a = 0;

for (int i = 0; i < count; i++) {

a += 5;

}

}

});

thread.start();

int b = 0;

for (long i = 0; i < count; i++) {

b--;

}

thread.join();

long time = System.currentTimeMillis() - start;

System.out.println("Concurrency:" + time + "ms, b = " + b);

}

}

测试结果:循环次数串行执行耗时/ms并发执行耗时/ms1亿139108

1000万1614

100万66

10万24

1万03

观察可知,当并发执行累加操作不超过百万次时,速度会比串行执行累加操作要慢。正是因为线程有创建和上下文切换的开销,所以才会出现这种现象。

1.3 切换查看

在Linux系统下可以使用vmstat命令来查看上下文切换的次数,下面是利用vmstat查看上下文切换次数的示例:[root@localhost vagrant]# vmstat 1

procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----

r b swpd free buff cache si so bi bo in cs us sy id wa st

2 0 0 628740 2068 218576 0 0 1085 40 136 324 2 3 95 0 0

0 0 0 628748 2068 218576 0 0 0 0 48 86 0 0 100 0 0

0 0 0 628748 2068 218576 0 0 0 0 35 76 0 0 100 0 0

0 0 0 628748 2068 218576 0 0 0 0 41 82 0 0 100 0 0

0 0 0 628748 2068 218576 0 0 0 0 35 82 0 0 100 0 0

0 0 0 628748 2068 218576 0 0 0 0 39 78 0 0 100 0 0

0 0 0 628748 2068 218576 0 0 0 0 38 88 0 0 100 0 0

0 0 0 628748 2068 218576 0 0 0 0 45 80 0 1 99 0 0

0 0 0 628748 2068 218576 0 0 0 0 34 77 0 0 100 0 0

0 0 0 628748 2068 218576 0 0 0 0 40 87 0 0 100 0 0

vmstat 1指每秒计数一次,cs表示上下文切换的次数。可以看到,上下文每秒钟切换80~90次左右。

1.4 切换原因

对于我们经常使用的抢占式操作系统而言,引起线程上下文切换的原因大概有以下几种:当前执行任务的时间片用完之后,系统CPU正常调度下一个任务。

当前执行任务碰到IO阻塞,调度器将此任务挂起,继续下一任务。

多个任务抢占锁资源,当前任务没有抢到锁资源,被调度器挂起,继续下一任务。

用户代码挂起当前任务,让出CPU时间。

硬件中断。

Java程序中,线程上下文切换的主要原因可分为:程序本身触发的自发性上下文切换sleep、wait、yield、join、park、synchronized、lock等方法

系统或虚拟机触发的非自发性上下文切换线程被分配的时间片用完、JVM垃圾回收(STW、线程暂停)、执行优先级高的线程

在Java虚拟机中,由程序计数器(Program Counter Register)存储CPU正在执行的指令位置、即将执行的下一条指令的位置。

1.5 减少上下文切换

减少上下文切换的方法有无锁并发编程、CAS算法、使用最少线程和使用协程。无锁并发编程。多线程竞争时,会引起上下文切换,所以多线程处理数据时,可以用一些办法来避免使用锁,如将数据的ID按照Hash取模分段,不同的线程处理不同段的数据。

CAS算法。Java的Atomic包使用CAS算法来更新数据,而不需要加锁。

使用最少线程。避免创建不需要的线程,比如任务很少,但是创建了很多线程来处理,这样会造成大量线程都处于等待状态。

协程。在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换。

参考资料

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值