并发必备基础知识汇总

用户头像
独钓寒江雪
关注
发布于: 2020 年 07 月 22 日
并发必备基础知识汇总

学习任何一门语言都是先要掌握其基本的学术概念,在掌握概念的基础上,再去深究其内部实现原理。学习并发编程,那么也需要先掌握其基本概念,然后去学习它的实现原理,最后将其应用到合适的场景中。



一、必备基础概念



1.同步(Synchronous)和异步(Asynchronous)



同步:同步方法一旦调用,那么调用该同步方法的线程必须等待这个同步方法执行完毕之后才可以继续执行后续的代码,后续的代码可以实时使用同步方法返回的数据。



异步:异步方法更像一种消息传递,当主线程调用该异步方法后,就好像将异步方法的执行消息发给了另外一个线程,异步方法的方法体会在另外一个线程执行,并不会阻碍当前主线程的后续流程,也就是说,主线程调用异步方法后,会立马执行异步方法后的其他流程,不会阻塞等待该异步方法执行结束。如果异步方法有返回值,那么在异步方法执行结束后,会通知主线程。



2.并发(Concurrency)和并行(Parallelism)



并发:并发侧重于多个任务的“交替”执行,一会执行任务A,一会执行任务B,系统会在两者之间切换。



并行:并行侧重于多个任务同时执行,是真正意义上的“同时执行”。



在单核CPU系统中,一个CPU每次执行一条指令,那么就不可能同时执行多个命令,这个时候并行就不可能发生,但是可以发生多进程或者多线程中的并发任务,系统可以不停地切换任务来实现并发。实现并行的基本条件是计算机是一个多核CPU的计算机。



3.临界区



临界区:临界区是一个可以被多个线程共享的区域,它内部存放的数据属于公共资源,但是访问或者修改该区域的数据,只能由一个线程来完成,其他的线程只能等待前面的线程访问或者修改完临界区数据后才可以继续访问或者修改。



4.阻塞(Blocking)和非阻塞(Non-Blocking)



阻塞:阻塞和非阻塞是用来形容线程之间的相互影响,阻塞就表示一个线程占用了临界区资源后,其他线程必须等待前面的线程完成对临界区资源的访问后才可以继续访问临界区资源,阻塞会导致后续线程的挂起,如果当前线程一直占用着资源,那么后续的线程将一直等待,有可能会导致超时甚至是雪崩。



非阻塞:非阻塞是指线程之间都不会相互影响,每个线程都是不断地向前执行,线程之间不会相互等待。



5.死锁(Deadlock)、饥饿(Starvation)和活锁(Livelock)



死锁,饥饿以及活锁都是描述线程的活跃性词组。当多线程出现上述三者之一,那么表明在多线程环境下的程序也许无法再正常执行下去了。



死锁:死锁是一个多线程环境中十分严重的问题,多个线程互相占用着其他线程继续执行下去基本资源并且没有释放,导致多个线程都挂起等待,这种情况发生后基本很快导致程序的崩溃。



饥饿:饥饿是指一个或者多个线程因为某种原因无法得到其正常运行的基本资源,导致该线程一直无法继续执行。原因包括该线程优先级较低,而优先级较高的线程往往优先占用资源等。



活锁:活锁是指线程间秉承着“谦让”的原则,互相主动将资源让给其他资源来使用,保证其他线程的正常执行,但是往往线程间总是因为谦让而导致线程不停地在线程中跳动,没有一个线程可以正常地拿到所有资源正常执行。



二、并发级别



多线程环境中,最关心的就是对临界区资源的访问了,对临界区资源的并发访问必须得到控制。根据并发的策略,一般将并发的级别分为阻塞、无饥饿、无障碍、无锁以及无等待几种。



1.阻塞



一个线程是阻塞的,那么在其他线程释放资源之前且当前线程获得资源之前,当前资源是无法继续执行的,当一个临界区资源被synchronized关键字修饰或者使用重入锁时,那么得到的线程就是阻塞的线程。



2.无饥饿(Starvation-Free)



如果多线程环境下,部分线程具备较高优先级,那么低优先级的线程往往在线程调度过程中占据劣势,分配临界区资源的时候,往往无法满足低优先级线程的需求,导致低优先级线程不能正常执行。很显然,这种情况是不公平的,会导致低优先级线程产生饥饿。这种非公平锁允许优先级较高的线程插队,从而导致低优先级线程产生饥饿。如果在线程调度中,系统将所有线程一视同仁,那么优先级较高的线程也必须得遵循“先来后到”的原则,那么就可以解决低优先级线程无法执行的风险。



3.无障碍(Obstruction-Free)



无障碍是一种最弱的非阻塞调度,两个线程如果无障碍地执行,那么不会因为临界区共享数据的问题而导致一方线程被挂起。也就是说,多个线程都可以同时进入到临界区,都可以对临界区内的共享数据尽心修改,在线程保存修改的时候,如果发现数据异常,那么将进行回滚,如果没有发现异常,那么就可以完成对临界区共享数据的修改,正常执行后续的流程。



对比阻塞和无障碍的并发级别,可以理解为阻塞为悲观的控制策略,因为阻塞是强制挂起其他线程,保证数据的安全性,而无障碍可以理解为乐观的控制策略,它乐观地认为多个线程操作临界区共享数据不会发生冲突,或者是认为发生冲突的概率非常低,那么多个线程都可以成功地走出临界区,如果发生冲突,那么就回滚。



对于无障碍的控制策略,可以通过“一致性标记”来实现,基本原理就是在线程操作临界区数据之前,读取当前临界区数据的一个标记,当线程修改完数据后,那么再次读取这个标记,如果标记没有修改,那么就认为在该线程操作数据期间没有其他线程修改过数据,那么就可以直接保存数据并对标记进行修改,如果线程修改完数据发现标记被其他线程更改了,那么就认为资源访问发生了冲突,当前线程数据进行回滚。



4.无锁(Lock-Free)



无锁的并行都可以理解为是无障碍的, 在无锁的场景下,临界区的数据可以同时被多个线程同时访问,但是始终会保证有一个线程能在有限步骤内完成对临界区数据的访问并安全离开临界区。



5.无等待(Wait-Free)



相对于无锁策略来说,无等待的并发级别是对前者的一种扩展,无锁要求有一个线程成功访问到临界区并安全离开即可,而无等待则要求所有的线程都必须在有限的步骤内完成对临界区的访问,并且不会引起饥饿问题。



无等待的并发策略的一个典型应用案例就是RCU(Read Copy Update),基本的原理思想是:所有线程对数据的读取是不加以控制的,所有线程都可以同时对数据进行读取,涉及到数据的写,都是在拷贝原有数据到副本后进行的,所有的写操作都是在副本上进行,完成后在合适的时机将数据更新会原有的数据引用上。Java集合中有一个CopyOnWriteArrayList就是无等待并发策略的一个典型应用实例,多线程对该集合的读是无等待的,对集合的写是在集合副本上进行的,并在合适的时机将数据更新到原有集合引用上。



了解更多干货,欢迎关注我的微信公众号:爪哇论剑(微信号:itlemon)



发布于: 2020 年 07 月 22 日 阅读数: 35
用户头像

独钓寒江雪

关注

还未添加个人签名 2018.09.30 加入

Java码农一枚。

评论

发布
暂无评论
并发必备基础知识汇总