起源
首先我们先绕开这个话题来聊一聊上下文切换路径,上下文切换路径可以顾名思义来理解就是不间断的分配CPU时间片来实现这个机制。时间片就是CPU分配给各个线程的时间因为时间片是非常的短暂,所以我们察觉不到,一般为几十毫秒
CPU通过时间片分配算法来循环各个线程的时间,当前任务执行一个时间片之后会切换到下一个任务,但是在切换之后会保存上一个任务的状态,以便下次在切换回来的时间,再次加载这个任务的状态,所以任务从保存到在加载的过程就是一次上下文切换
举一反三
就好比我们在看一本书,中途看到不懂得英文单词,这时我们需要去百度翻译查询是查询完之后还是要来看这本英文书,这个道理是与上下文切换一个道理,但是发现没有上下文切换的途中是不是消耗了时间去查阅我们的英文单词啊,这样对读书的效率是有影响的吧,所以说,同上!上下文切换也会影响多线程的
执行速度
测试
测试我们分别用1千、1万、10万、100万、1000万以及亿级别的数量来做测试,并且查看查出的时间差来对多线程与串行之间作为对比。具体为下列代码可查看。我这边就做1万与一亿来做测试,这样看来的对比会更直观。
一万数据量的查询
package demo1;
/**
* @Author 藤井大叔
* @Date 2021-09-17
* @projectName 并发案例一
* @description 并发与比串行之间的执行速度
*/
public class ContextDemo {
private static final long count = 10000;
/**
* 并发案例
* @throws InterruptedException
*/
private static void comcurrency() throws InterruptedException {
long start = System.currentTimeMillis();
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
int a = 0;
for (long 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("并发查询时间为:"+time+"ms,数据量为:"+b);
}
/**
* 串行案例
*/
private static void serial(){
long start = System.currentTimeMillis();
int a = 0;
for (long i = 0;i<count;i++){
a+=5;
}
int b =0;
for (long i=0;i<count;i++){
b--;
}
long time = System.currentTimeMillis()-start;
System.out.println("串行查询时间为:"+time+"ms,数据量为:"+b);
}
public static void main(String[] args) throws InterruptedException {
comcurrency();
serial();
}
}
执行结果为:
并发查询时间为:2ms,数据量为:-10000
串行查询时间为:0ms,数据量为:-10000
在一万的数据量可发现并发执行的速度并非比串行快,下面在对亿级别的数据量做一个测试,我这边就不做代码而是直接将结果粘贴至下了,测试只需在上面的总数改下即可
亿级别数据量执行结果为:
并发查询时间为:45ms,数据量为:-100000000
串行查询时间为:76ms,数据量为:-100000000
在亿级别的数据量上确实多线程的执行速度要比串行快很多。下面我也分别对各个级别的做了相关测试。具体如下
数量级别 | 并发(/ms) | 并行(/ms) |
---|---|---|
1千 | 1 | 0 |
1万 | 2 | 0 |
10万 | 3 | 2 |
100万 | 4 | 5 |
1000万 | 11 | 13 |
1亿 | 45 | 76 |
上面数据不为准确数据,因为是经过多次执行而选定的,不过大致认为如果说在并发执行不过百万级别的时候,当然并发还是要比串行慢的,为什么并发执行的会比串行慢呢?原因就是线程中有创建与上下文切换的开销,这也是需要时间成本的,所以在百万级以下的数据量并发还不如串行合适
防止方式
竟然已知耗时的原因就是出现在上下文切换上了,那么怎么去解决这个问题呢?这里有以下几种方案
无锁并发编程
无锁编发编程,多线程竞争锁时,会引起上下文切换,所以在多线程处理数据的时候,可以用一些办法来避免使用锁,如何将数据的ID按照Hash算法取模分段,不同的线程处理不同的段的数据
CAS算法
CAS算法,Java的Atomic包括使用CAS算法来更新数据,而不需要加锁
使用最少的线程
使用最少的线程,避免出现不需要的线程,比如任务量少,但是创建了很多线程来处理,这样会造成大量线程都处于等待状态
使用协议
在单线程中使用多任务的调度,并在单线程里维持多个任务间的切换
总结
若要前行,就要离开你现在停留的地方!