个人公众号: :✨✨✨ 可为编程✨
个人信条: 为与不为皆为可为
本篇简介: 本篇记录高并发必须知道的几个概念,如有出入还望指正。关注公众号【可为编程】回复【面试】领取2023年最新面试题!!!
同步和异步通常来形容一次方法调用,同步方法调用一旦开始,调用者必须等到方法调用返回后,才能继续后续的行为。异步方法调用更像一个消息传递,一旦开始,方法调用就会立即返回,调用者就可以继续后续的操作。而异步方法通常会在另外一个线程中“真实”地执行。整个过程,不会阻碍调用者的工作。
上图中显示了同步方法调用和异步方法调用的区别。对于调用者来说,异步调用似乎是一瞬间就完成的。如果异步调用需要返回结果,那么当这个异步调用真实完成时,则会通知调用者。
并发和并行是两个非常容易被混淆的概念。他们都可以表示两个或者多个任务一起执行,但是侧重点有所不同。并发偏重于多个任务交替执行,而多个任务之间有可能还是串行的,而并行是真正意义上的“同时执行”,下图很好地诠释了这点。
从严格意义上来说,并行的多任务是真的同时执行,而对于并发来说,这个过程只是交替的,一会执行任务A,一会执行任务B,系统会不停地在两者之间切换。但对于外部观察者来说,即使多个任务之间是串行并发的,也会造成多任务间并行执行的错觉。
并发说的是在一个时间段内,多件事情在这个时间段内交替执行。
并行说的是多件事情在同一个时刻同时发生。
实际上,如果系统内只有一个CPU,而使用多进程或者多线程任务,那么真实环境中这些任务不可能是真实并行的,毕竟一个CPU一次只能执行一条指令,在这种情况下多进程或者多线程就是并发的,而不是并行的(操作系统会不停地切换多任务)。真实的并行也只可能出现在拥有多个CPU的系统中(比如多核CPU)。
关注公众号【可为编程】回复【面试】领取2023年最新面试题!!!
临界区用来表示一种公共资源或者说共享数据,可以被多个线程使用,但是每一次只能有一个线程使用它,一旦临界区资源被占用,其他线程要想使用这个资源就必须等待。
比如,一个办公室里有一台打印机,打印机一次只能执行一个任务。如果小王和小明同时需要打印文件,很明显,如果小王先发了打印任务,打印机就开始打印小王的文件,小明的任务就只能等待小王打印结束后才能打印,这里的打印机就是一个临界区的例子。
在并行程序中,临界区资源是保护的对象,如果意外出现打印机同时执行两个任务的情况,那么最有可能的结果就是打印出来的文件是损坏的文件,它既不是小王想要的,也不是小明想要的。
阻塞和非阻塞通常用来形容很多线程间的相互影响。比如一个线程占用了临界区资源,那么其他所有需要这个资源的线程就必须在这个临界区中等待。等待会导致线程挂起,这种情况就是阻塞。此时,如果占用资源的线程一直不愿意释放资源,那么其他线程阻塞在这个临界区上的线程都不能工作。
非阻塞的意思与之相反,它强调没有一个线程可以妨碍其他线程执行,所有的线程都会尝试不断向前执行。
死锁、饥饿和活锁都属于多线程的活跃性问题。如果发现上述几种情况,那么相关线程就不再活跃,也就是说它可能很难再继续往下执行了。
死锁应该是最糟糕的一种情况了(当然,其他几种情况也好不到哪里去),如下图显示了一个死锁的发生:
线程1有用资源A,占用A,对A上锁,线程2拥有资源B占用B,对B上锁,同时线程1请求资源B,线程2请求资源A,又因为资源都被占用,彼此都不会释放资源,那么这个状况将永远持续下去,谁都不可能拥有对方的资源,死锁是一个很严重的并且应该避免和实时小心的问题,后面的文章中会做更详细的讨论。
关注公众号【可为编程】回复【面试】领取2023年最新面试题!!!
饥饿是指线程因资源获取不足而无法继续执行的情况。可能的原因包括线程优先级过低,导致高优先级的线程不断抢占资源,使低优先级线程无法正常运行。在自然环境中,雏鸟因食物有限而无法获得足够的食物,这种情况与线程的饥饿相似。另外,某个线程一直占用关键资源,导致其他需要该资源的线程无法正常执行,也属于饥饿的一种。与死锁相比,饥饿问题可能在一段时间后得到解决,例如高优先级的线程完成任务后不再占用资源。
活锁是一种有趣的状况,类似于你和电梯外的人互相礼让导致无法通过的情况。如果线程的智能不足,且遵循“谦让”原则,主动释放资源给其他线程使用,就会导致资源在两个线程之间不断跳动,而没有一个线程能够同时获得所有资源并正常执行。这种情况就是活锁。
线程(Threads)与进程(Processes)
进程:进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。程序是指令、数据及其组织形式的描述,进程是程序的实体。
进程的特质
动态性:进程是程序的一次执行过程,是临时的,有生命期的,是动态产生,动态消亡的
并发性:任何进程都可以同其他进程一起并发执行
独立性:进程是系统进行资源分配和调度的一个独立单位
结构性:进程由程序,数据和进程控制块(PCB)三部分组成
线程:是程序执行的最小单元,使用多线程而不是多进程去进行并发程序的设计,是因为线程间的切换和调度的成本远远小于进程。
线程的状态
New:表示刚刚创建的线程,这种线程还没有开始执行
RUNNABLE:运行状态,线程的start()方法调用后,线程会处于这种状态
BLOCKED:阻塞状态。当线程在执行的过程中遇到了synchronized同步块,但这个同步块被其他线程已获取还未释放时,当前线程将进入阻塞状态,会暂停执行,直到获取到锁。当线程获取到锁之后,又会进入到运行状态(RUNNABLE)
WAITING:等待状态。和TIMEWAITING都表示等待状态,区别是WAITING会进入一个无时间限制的等待,而TIMEWAITING会进入一个有限的时间等待,那么等待的线程究竟在等什么呢?一般来说,WAITING的线程正式在等待一些特殊的事件,比如,通过wait()方法等待的线程在等待notify()方法,而通过join()方法等待的线程则会等待目标线程的终止。一旦等到期望的事件,线程就会再次进入RUNNABLE运行状态。
TERMINATED:表示结束状态,线程执行完毕之后进入结束状态。
线程的所有状态在java.lang.Thread中的State枚举中定义。
关注公众号【可为编程】回复【面试】领取2023年最新面试题!!!
感兴趣的小伙伴可以自行查阅。