关于并发必须知道的几个概念:

关于并发必须知道的几个概念:
关于并发必须知道的几个概念:

同步和异步

同步和异步通常来形容一次方法调用,同步方法调用一旦开始,调用者必须等到方法调用返回后,才能继续后续的行为。异步方法调用更像一个消息传递,一旦开始,方法调用就会立即返回,调用者就可以继续后续的操作。而异步方法通常会在另外一个线程中“真实”地执行。整个过程,不会阻碍调用者的工作。

线程同步和线程异步

线程同步:

即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作, 其他线程才能对该内存地址进行操作,而其他线程又处于等待状态,实现线程同步的方法有很多,临界区对象就是其中一种。

在java中,线程同步的实现机制有很多种。比如最简单的synchronized关键字,以及读写锁,重入锁等锁机制。

线程异步:

指的是当有一个线程在调用某个方法的时候,这个方法将在执行的时候,将会在独立的线程中被执行,调用者无需等待它的完成,即可继续其他的操作。

在spring3.0之后,提供了@Async注解来开辟一个独立的线程用于异步执行方法。还可以使用自己创建线程池ThreadPoolExecutor,手动调用的方法来开辟异步线程。

并发和并行

并发:

在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处理机上运行。

对于并发来说,多个任务的执行并不是同时的。而是一会执行任务A,一会执行任务B。比如电脑上我们又在打游戏,又在听音乐。这个过程实际上就是并发的,系统会不停的在多个任务之间切换,这个过程我们感知不到而已。

这样理解起来可能有一些抽象,举个例子,比如你正在打游戏,这个时候你的女朋友打来了电话。于是你不得不先停下游戏,开始和女朋友聊天。聊完天后再去打游戏。

并行:

当系统有一个以上CPU时,当一个CPU执行一个进程时,另一个CPU可以执行另一个进程,两个进程互不抢占CPU资源,可以同时进行,这种方式我们称之为并行(Parallel)。

并行的关键点在于有同时处理多个任务的能力。

举个例子,比如你正在打游戏,这个时候你的女朋友打来了电话。这个时候你可能既不想放弃游戏,也不想放弃女朋友。于是你不得不一边打着游戏,一边和女朋友聊天。

事实上,并发和并行的区别就在于,并发是多个任务交替着执行的。而并行指的是多个任务同时执行。

临界区

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

在java中,一般使用最简单的synchronized关键字,以及读写锁,重入锁等锁机制来保护临界区资源。

阻塞和非阻塞

阻塞:

阻塞指的是如果一个线程占用了临界区资源,因为临界区资源每次只允许一个线程使用,其他想访问临界区资源的线程就必须等待,导致线程挂起,这个过程就是阻塞。

比如我们使用synchronized来修饰方法的时候,每次只有一个线程可以调用这个方法,而其它线程只能等待这个线程调用完成,才可以由cpu分配下面哪个线程再去调用。

非阻塞:

非阻塞与阻塞的意思相反。它强调没有一个线程可以妨碍其他线程执行,所有的线程都会尝试不断向前执行。

死锁、饥饿和活锁

死锁:

死锁是我们编程中最糟糕的一种情况。是指两个或两个以上的进程(或线程)在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

假设当前情况是线程A已经获取资源R1,线程B已经获取资源R2,之后线程A尝试获取资源R2,这个时候因为资源R2已经被线程B获得了,所以线程A只能阻塞直到线程B释放资源R2。另一方面,线程B在已经获得资源R2的前提下尝试获取由线程A持有的资源R1,那么由于资源R1已经被线程A持有了,那么线程B只能被阻塞直到线程A释放资源R1。这样线程A和线程B都在等待对方持有的资源,就造成了死锁。这种情形由一个专业术语:顺序死锁。还有动态死锁、协作死锁以及资源死锁,其实本质都一样:都因为在等待被其他线程占有的资源而造成整个程序无法继续向下执行。

饥饿:

当线程无法访问它所需要的资源而不能继续执行时,就发生了饥饿现象。引发饥饿最常见的资源就是CPU时钟周期。如果在Java应用程序中对线程的优先级使用不当,或者在持有锁的时候执行一些无法结束的结构,那么也可能导致饥饿。

通常尽量不要更改线程的优先级,只要改变了线程的优先级,程序的行为就将与平台相关,并且会导致发生饥饿的风险。。

活锁:

活锁是另一种形式的活跃性问题。该问题尽管不会阻塞线程,但也不能继续执行,因为线程将不断重复同样的操作,而且总会失败。活锁通常发生在处理事务消息中:如果不能成功处理某个消息,那么消息处理机制将回滚事务,并将它重新放到队列的开头。这样,错误的事务被一直回滚重复执行。这种形式的活锁通常是由过度的错误恢复代码造成的,因为它错误地将不可修复的错误认为是可修复的错误。

当多个相互协作的线程都对彼此进行相应而修改自己的状态,并使得任何一个线程都无法继续执行时,就导致了活锁。这就像两个过于礼貌的人在路上相遇:他们彼此让路,然后在另一条路上相遇,然后他们就一直这样避让下去。

要解决这种活锁问题,需要在重试机制中引入随机性。例如在网络上发送数据包,如果检测到冲突,都要停止并在一段时间后重发。如果都在1秒后重发,还是会冲突。所以引入随机性可以解决该类问题。

关于并发必须知道的几个概念: