并发编程中的几个名词概念

时间:2022-07-08 20:56:21

现在,高并发,高流量已成为行业的热点,并且各种高并发的技术也是层出不穷,如论是官方文档还是市面上的各种书籍,我们在阅读的时候都会遇到一些专业名词,理解这些专业名词之后,才能更好的理解内容。

一、同步与异步

介绍:

同步和异步通常来形容一次方法调用。

解释一:同步方法调用一旦开始,调用者必须等到方法的调用返回后,才能继续后续的行为。异步方法调用更像一个消息传递,一旦开始,方法调用就会立即返回,调用者可以继续后续的操作。

解释二:同步就是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个进程将会一直等待下去,直到收到返回信息才继续执行下去。异步是指进程不需要一直等下去,而是继续执行下面的操作,不管其他进程的状态。当有消息返回时系统会通知进程进行处理,这样可以提高执行的效率。

画图解释:

并发编程中的几个名词概念

现实场景

毕业季,需要写毕业论文,中午到饭点了,我们需要补充能量吃饭,怎么解决呢?这时有两个选择:

1. 出门就餐:出门去食堂或者去快餐店就餐,这时你可能需要点餐,等师父烹饪,等服务员上菜,然后就餐,等你吃完之后你才可以回去继续毕业论文的书写。

2. 点卖外:通过外卖平台订购外卖,在等外卖送来的过程中,你可以继续书写论文或做其他的事。

 出宿舍就餐就是一个同步方法的过程,两件事的执行时间是顺序连着的;点外卖就是一个异步的过程,两件事的执行时间是并行异步的。

 二、并发和并行

介绍

解释一:并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔发生。

解释二:并行是在不同实体上的多个事件,并发是在同一实体上的多个事件

解释三:在一台处理器上“同时”处理多个任务,在多台处理器上同时处理多个任务。如hadoop分布式集群

并发偏重于多个任务交替执行,而多个任务之间有可能还是串行的。而并行是真正意义上的“同时执行”。

严格意义上来说,并行的多个任务是真实的同时执行的,而对于并发来说,这个过程只是交替的,一会儿执行A,一会儿执行B,系统会不停的在两者之间切换。但对于外部观察者来说,即使多个任务之间是串行并发的,也会造成多任务间是并行执行的错觉。

实际上,如果系统内只有一个CPU,而使用多进程或者多线程任务,那么真实环境中这些任务不可能是真实并行的,毕竟一个CPU一次只能执行一条指令,这种情况下多进程或者多线程就是并发,而不是并行(操作系统会不停切换多个任务)。真实的并行也只可能出现在拥有多个CPU的系统中(比如多核CPU)。

画图解释:

并发编程中的几个名词概念

现实场景

爬山,国内一般在相对较高的名山,为照顾游客都会提供两种方式登山:一种是游客自己凭借双腿,另一种就是借助缆车。

1. 游客自己凭借双腿登山:游客自己凭双腿登山的时候,一般都是走走停停,一是为了歇息,还有就是停下来看风景,因为不停下来,是没有办法专心看风景的,登山的过程中,需要注意脚下、注意安全。登山和看风景是交替并发执行的。这就是一个并发的过程。

2. 借助缆车:游客花点钱,坐缆车游玩登山,这时我们可以不必走走停停,也不需要关注脚下。我们可以一边往山顶移动,一边欣赏沿途的风景。登山和看风景是两件同时进行的事情。这就是一个并行的过程。

三、临界区

介绍

临界区用来表示一种公共资源或者说是共享数据,可以被多个线程使用。但是每一次只能有一个线程使用它,一旦临界区资源被占用,其他线程要想使用这个资源,就必须等待。

画图解释

并发编程中的几个名词概念

 

现实场景

打印机,我们工作的时候肯定会经常用到打印机,打印机就是一个临界区的最好例子。一台打印机一次只能执行一个任务,如果 A 和 B 同时需要打印文件,很显然,如果 A 先发下打印的任务,打印机就开始打印 A 的文件。B 的任务就只能等待 A 打印结束之后才能打印。

四、阻塞和非阻塞

阻塞和非阻塞通常形容多线程之间的相互影响。比如一个线程占用了临界区资源,那么其他所有需要这个资源的线程就必须在这个临界区中进行等待。等待会导致线程挂起,这种情况就是阻塞。此时,如果占用资源的线程一直不愿意释放资源,那么其他所有阻塞在这个临界区上的线程都不能工作。

当我们使用synchronized关键字,或者重入锁时(后续会有文章详细的介绍Java中锁的分类),我们得到的就是阻塞的线程。无论是synchronized或者重入锁,都会试图在执行后续代码前,得到临界区的锁,如果得不到,线程就会被挂起等待,直到占有了所需资源为止。

非阻塞允许多个线程同时进入临界区 。

五、死锁,饥饿,活锁

死锁,饥饿和活锁都属于多线程的活跃性问题。如果发现上述几种情况,那么相关线程可能就不再活跃,也就说它可能很难再继续往下执行了。

死锁:所谓死锁,是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

例如,如果线程1锁住了A,然后尝试对B进行加锁,同时线程2已经锁住了B,接着尝试对A进行加锁,这时死锁就发生了。线程1永远得不到B,线程2也永远得不到A,并且它们永远也不会知道发生了这样的事情。为了得到彼此的对象(A和B),它们将永远阻塞下去。这种情况就是一个死锁。

借助《实战Java高并发程序设计》中的图:

并发编程中的几个名词概念

A、B、C、D四辆小车在这种情况下都无法继续行驶了。他们彼此之间相互占用了其他车辆的车道,如果大家都不愿意释放自己的车道,那么这个状态将永远的维持下去,谁都不可能通过。

饥饿: 饥饿是指某一个或者多个线程因为种种原因无法获得所需要的资源,导致一直无法执行。 

产生这种情况的原因是多种的,可能是它的线程优先级太低,而高优先级的线程不抢占它需要的资源,导致低优先级线程无法工作。也可能是某个线程一直占着关键资源不放,导致其他需要这个资源的线程无法正常执行。

活锁:活锁指的是任务或者执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试,失败,尝试,失败。

活锁可以认为是一种特殊的饥饿。

列举现实生活中的一个例子:一条狭窄的道路,A 和 B 迎面相遇了,缘分使然,A绅士的想让出道路让B先过,这时B也保持良好的淑女形象想让出道路让A先过,导致两人都避让了;两人尴尬一笑,之后A想着B既然让了,就准备先过,巧的是,这时B心里也想着,A既然让了,不如先过,两人又撞上了;然后又开始礼貌性的相互避让,避让之后各自又想先走结果又撞上了,结果两人都没过去。这种情况就是活锁。

线程都秉承着"谦让"的原则,主动将资源释放给他人使用,那么就会出现资源不断在两个线程之间跳动,而没有一个线程可以同时拿到所有资源而正常执行。

 

参考书籍:《实战Java高并发程序设计》,《Java并发编程的艺术》