前言
今天我们开始接触并发编程的讲解,首先我们要知道并发编程的目的是什么?一个目的,为了让程序员运行的更快,但是有一点我们要知道,并不是启动越多的线程就能让程序更大限度的并发执行,在并发编程的开发中,如果希望使用更多的线程来加快程序的运行,一般我们会面临下面几种挑战:上下文切换问题,死锁、以及受硬件和软件资源的限制问题,那么我们一个个来说下吧,让我们开始今天的并发编程之旅吧。
上下文切换
什么是上下文切换?
即使是单核处理器,也支持多线程执行代码,CPU通过给每个线程分配CPU时间片来实现这个机制。时间片是CPU分配给每个线程的时间,因为时间片很短,所以CPU通过不断的切换线程执行,给用户感官上造成多线程执行的状态。
CPU通过时间分配算法来执行任务,当前任务执行一个时间片后,就会切换到下个任务,此时,会保存切换前上个任务的状态,目的是下次切换回这个任务时,可以继续加载该任务的状态,我们把任务从保存到再加载的这个过程就称为是一次上下文切换。
多线程一定比单线程快?
答案是否定的,多线程环境下,因为线程有创建和上下文切换的时间开销,所以,多线程并不一定比单线程快;只能说在有限的资源环境下,线程数量达到某个值之前会比单线程处理速度快,超过这个值速度就会下降,甚至比单线程速度低,如下图:
如何减少上下文切换?
减少上下文切换主要有如下几种方式:
-
无锁并发编程:多线程竞争锁时会引起上下文切换,所以多线程处理数据时,可以用一些方法避免使用锁,如将任务的编号按照Hash取模分段,不同线程处理不同段的任务,即将任务或者数据分块处理,各个线程处理独立的块;
-
CAS算法:Java提供Atomic包使用CAS算法更新数据,取代加锁;
-
使用最少线程:避免创建不需要的线程,如少量任务时创建大量线程,就会造成大部分线程处于等待状态,减少等待线程的创建;
-
协程:单线程环境下,实现多任务调度,并维持多个任务之间的切换;
死锁
死锁最常见的就是两个线程之间相互持有对方的锁,都在等待对方释放,而造成死锁,使得两个线程一直处于等待状态,因此我们应该要避免这样的场景出现,主要可以从下面几个方面考虑:
-
避免一个线程同时获取多个锁
-
避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源
-
尝试使用定时锁,使得锁能得到一个释放(由tryLock实现)
-
对于数据库锁,加锁和解锁必须在一个数据库连接中,否则会出现解锁失败的情况
资源限制
什么是资源限制?
资源限制指的是在进行并发编程时,程序的执行速度受限于计算机硬件资源和软件资源;
硬件资源主要包括:带宽的上传/下载速度、硬盘的读写速度和CPU的处理速度
软件资源主要包括:数据库的连接数和socket连接数等
资源限制引发的问题?
并发编程中通过将串行执行的代码改为并发执行来加快处理速度,但是如果将某段代码改为并发执行之后,由于受到资源限制,代码本质上仍然在串行执行,那么由于增加了上下文切换和资源调度的时间,程序的速度不仅不会加快,反正更慢。
如何解决资源限制问题?
硬件资源受限:可以考虑使用集群并发执行程序
软件资源受限:可以考虑使用资源池将资源复用,最大化利用资源