并发编程的目的是
为
了
让
程序运行得更快,但是,并不是启
动
更多的
线
程就能
让
程序最大限度地并发执
行。在
进
行并
发编
程
时
,如果希望通
过
多
线
程
执
行任
务让
程序运行得更快,会面临
非常多的挑
战
,比如上下文切
换
的
问题
、死
锁
的
问题
,以及受限于硬件和
软
件的
资
源限制问题
目录
上下文切换
多线程一定快吗?
什么情况下适合使用多线程呢?
如何减少上下文的切换
上下文切换
即使是
单
核
处
理器也支持多
线
程
执
行代
码
,
CPU
通
过给
每个
线
程分配
CPU
时间
片来
实现这个机制。
时间
片是
CPU
分配
给
各个
线
程的
时间
,因
为时间
片非常短,所以
CPU
通
过
不停地切换线程
执
行,
让
我
们
感
觉
多个
线
程是同
时执
行的,
时间
片一般是几十毫秒。
CPU
通
过时间
片分配算法来循
环执
行任
务
,当前任
务执
行一个
时间
片后会切
换
到下一个任务
。但是,在切
换
前会保存上一个任
务
的状
态
,以便下次切
换
回
这
个任
务时
,可以再加
载这个任务
的状
态
。所以任
务
从保存到再加
载
的
过
程就是一次上下文切
换
。
可见切换上下文是会影响线程的执行速度的。同时也引发了我们的下一个问题。
多线程一定快吗?
先给出一段代码,说一说你的看法
public class ConcurrencyTest {
private static final long count = 10000l;
public static void main(String[] args) throws InterruptedException {
concurrency();
serial();
}
private static void concurrency() 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--;
}
long time = System.currentTimeMillis() - start;
thread.join();
System.out.println("concurrency :" + time+"ms,b="+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("serial:" + time+"ms,b="+b+",a="+a);
}
}
首先答案肯定是“不一定”的,比如下面给出我执行的结果
这里的单线程就比上面的多线程要快,但是你多执行几次就肯定会发现其他的情况。
这样的结果也佐证了上述问题的答案是“不一定”的,要看具体情况而言。也进而抛出了我们的下一个问题。
什么情况下适合使用多线程呢?
首先我们要先知道,多线程为什么有时候会比单线程慢,这是因为线程有创建和上下文切换的开销,这段时间cpu啥么也不会做,只在那里“等”,所以在cpu产生浪费比例大的时候可以使用多线程来。通过上下文切换和cpu的浪费比例来决定我们要使用的线程数。
如何减少上下文的切换
减少上下文切
换
的方法有无
锁
并
发编
程、
CAS
算法、使用最少
线
程和使用
协
程。
- 无锁并发编程。多线程竞争锁时,会引起上下文切换,所以多线程处理数据时,可以用一
- 些办法来避免使用锁,如将数据的ID按照Hash算法取模分段,不同的线程处理不同段的数据。
- CAS算法。Java的Atomic包使用CAS算法来更新数据,而不需要加锁。
- 使用最少线程。避免创建不需要的线程,比如任务很少,但是创建了很多线程来处理,这样会造成大量线程都处于等待状态。
- 协程:在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换。