并发是为了提升程序的执行速度,但并不是多线程一定比单线程高效,而且并发编程容易出错,若要实现正确且高效的并发,就要在开发过程中时刻注意一下三个问题:
1、上下文切换
2、死锁
3、资源限制
一、上下文切换
当一条线程的时间片用完后,操作系统会暂停该线程,并保存该线程相应的信息,然后在随机选择一条线程去执行,这个过程就成为,线程的上下文切换
上下文切换的过程:
1、暂停正在执行的线程
2、保存该线程的相关信息(如:执行到哪一行,程序计算的中间结果等)
3、从就绪队列中随机选一条线程
4、读取该线程的上下文信息,继续执行
上下文切换是有开销的:
每次进行上下文切换的时候都需要保存当前线程的执行状态,并加载新线程先前的状态。如果上下文切换频繁,cpu花在上下文切换上的时间占比就会上升,而真正处理任务的时间占比就会下降,因此为了提高并发程序的执行效率,让cpu把时间花在刀刃上,我们需要减少上下文切换的次数。
如何减少上下文切换:
1、减少线程的数量
由于一个cpu每个时刻只能执行一条线程,而骄傲的我们又想让程序并发执行,操作系统只好不断地进行上下文切换来使我们从感官上觉得程序并发执行的行,因此,我们只要减少线程的数量,就能减少上下文切换的次数。然而如果线程数量已经少于cpu核数,每个cpu执行一条线程。照理说cpu不需要进行上下文切换了,但是事实并非如此。
2、控制同一把锁上的线程数量
如果多线程公用同一把锁,那么当一条线程获得锁后,其他线程就会阻塞;当该线程释放锁后,操作系统会在被阻塞的线程中选一条执行,从而又会出现上下文切换,因此,减少同一把锁上的线程数量也能减少上下文切换的次数。
3、采用无所并发编程
我们知道,如果减少同一把锁上线程数量就能减少上下文切换的次数,那么如果不用锁,是否就能避免因竞争锁而产生的上下文切换那?
答案是肯定的!但你需要根据以下两种情况挑选不同的策略:
1)需要并发执行的任务时无状态的:HASH分段,所谓无状态是指并发执行的任务没有共享变量,他们都独立执行,对于这种类型的任务可以按照id进行hash分段,每段用一条线程去执行。
2)需要并发执行的任务是有状态的:CAS算法,如果任务需要修改共享变量,那么必须控制线程的执行顺序,如果会出现安全问题,你可以给任务加锁,保证任务的原子性与可见性,但这会引起阻塞,从而发生上下文切换,为了避免上下文切换,你可以使用CAS算法,仅在线程内部需要更新共享变量时使用CAS算法更新,这种方式不会阻塞线程,并保证更新过程的安全性。
二、死锁
并发不当可能会产生死锁,产生死锁的原因:当多个线程互相等待已经被对方占用的资源时,就会产生死锁。示例:
package test;
public class SisuoTest {
private static Object lockA = new Object();
private static Object lockB = new Object();
public static void main(String[] args) {
Thread ta = new Thread(new Runnable() {
@Override
public void run() {
try {
while(true){
synchronized(lockA){
Thread.sleep(500);
synchronized(lockB){
System.out.println("线程1");
}
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
ta.start();
Thread tb = new Thread(new Runnable() {
@Override
public void run() {
try {
while(true){
synchronized(lockB){
Thread.sleep(500);
synchronized(lockA){
System.out.println("线程2");
}
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
tb.start();
}
}
死锁出现,两条线程互相等待已经被占用的资源,程序就死在这里了,死锁是并发编程中一个重要的问题,上面介绍的减少上下文切换只是为了提升程序的性能,而一旦产生死锁,程序就不能正确执行了!
如何避免死锁:
1)不要在一条线程中嵌套多个锁;
2)不要在一条线程中嵌套占用多个计算机资源;
3)给锁和资源加超时时间,如果你非要在一条线程中嵌套使用多个锁或占用多个资源,那你需要给锁、资源加超时时间,从而避免无限期的等待
三、计算机资源会限制并发
误区:线程越多速度越快
1)在并发编程中,并不是线程越多越好,有时候线程多了反而会拉低执行效率,原因如下:
线程多了会导致上下文切换增多,CPU花在上下文切换的时间增多后,花在处理任务上的时间自然就减少了。
1)在并发编程中,并不是线程越多越好,有时候线程多了反而会拉低执行效率,原因如下:
线程多了会导致上下文切换增多,CPU花在上下文切换的时间增多后,花在处理任务上的时间自然就减少了。
2)计算机资源会限制程序的并发度。
比如:你家网入口带宽10M,你写了个多线程下载的软件,同时开100条线程下载,那每条线程平均以每秒100k的速度下载,然而100条线程之间还要不断进行上下文切换,所以你还不如只开5条线程,每条平均2M/s的速度下载。
再比如:数据库连接池最多给你用10个连接,然而你却开了100条线程进行数据库操作,那么当10个用完后其他线程就要等待,从而操作系统要在这100条线程间不断进行上下文切换;所以与其这样还不如只开10条线程,减少上下文切换的次数。
比如:你家网入口带宽10M,你写了个多线程下载的软件,同时开100条线程下载,那每条线程平均以每秒100k的速度下载,然而100条线程之间还要不断进行上下文切换,所以你还不如只开5条线程,每条平均2M/s的速度下载。
再比如:数据库连接池最多给你用10个连接,然而你却开了100条线程进行数据库操作,那么当10个用完后其他线程就要等待,从而操作系统要在这100条线程间不断进行上下文切换;所以与其这样还不如只开10条线程,减少上下文切换的次数。
说了这么多只想告诉你一个道理:线程并不是越多越好,要根据当前计算机所能提供的资源考虑。
什么是“资源”?
资源分为硬件资源和软件资源:
硬件资源
硬盘读写速度
网络带宽
等
软件资源
Socket连接数
数据库连接数
等
如何解决资源的限制?
花钱买更高级的机器
根据资源限制并发度