作者:遥不V可及 | 来源:互联网 | 2023-10-13 13:35
从今天起开启一系列并发包的新线程,想要学习的童鞋,记得关注哦。这篇文章讲解下,并发包的基本概念,为以后学习并发做铺垫,所以还是很重要的。废话少说,开撸。目录同步和异步并发和并行临界
从今天起开启一系列并发包的新线程,想要学习的童鞋,记得关注哦。
这篇文章讲解下,并发包的基本概念,为以后学习并发做铺垫,所以还是很重要的。废话少说,开撸。
目录
- 同步和异步
- 并发和并行
- 临界区
- 阻塞和非阻塞
- 饥饿,死锁和活锁
- 并发级别
1.同步和异步
首先来说,同步和异步是基于函数/方法调用的,简单来说同步是会等待任务返回的。
而异步会立即返回,但这不代表着任务完成,他只是重新启了一个线程来执行当前任务。
2.并行和并发
并发和并行,外在看起来差不多的。有图可知,并行是两个任务同时执行,而并发则是做一会任务A,过会儿切换做一会任务B。所以单核CPU不能做并行的,只能是做并发。
3.临界区
临界区是用来表示公共资源或共享数据。可以被多个线程访问,但是每一次只能有一个线程使用它,一旦临界区资源被占用,其他线程就要等待,直到该线程使用完毕,其他线程才能访问它。
标题4.阻塞和非阻塞
阻塞和非阻塞是用来形容多线程之间的影响。当一个线程占用了临界区,其他线程都要等待,这就是阻塞。如果这个线程长期不释放临界区的资源,那么其他阻塞在这个临界区的线程都无法工作。非阻塞就是允许多个线程进入临界区。
所以阻塞的调度方式是一般的效率是比较低的。提倡使用非阻塞调度方式。
5.死锁、活锁和饥饿
1.死锁
先看下死锁的定义
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
定义都是冰冷的,还是举个炒栗子吧_
比如幼儿园里有一辆遥控玩具汽车,现在有AB两个小朋友都想玩,但是小朋友A抢到了遥控器,小朋友B抢到了汽车,两人互不想让,谁也不肯让对方先玩,这时如果没有老师介入,他俩可能就会一直这样僵持下去。这就形成了死锁。
但是虽然死锁不是什么好现象,但是死锁不会占用CPU资源,两个线程都是处于等待状态,不占CPU资源,这个一般来说,还是比较好发现的。
那如何解决死锁呢?
一般都是让线程按照同一个顺序获取资源,比如,线程1和2都先获取资源1,然后再获取资源2。这样就不会出现死锁现象了。
还说遥控汽车,解决这个问题,要预先定个规则,两个小朋友都要先去抢遥控器,抢到遥控器才能去抢汽车。这样就不会出现死锁了。
死锁说完了,来说一下活锁。
2.活锁
先看下定义
活锁指的是任务或者执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试—失败—尝试—失败的过程。处于活锁的实体是在不断的改变状态,活锁有可能自行解开。
活锁有可能自行解开,但是不容易被发现。活锁和死锁的区别就是,活锁是动态的,占用CPU资源的,而死锁是静态的,不占用CPU资源。
再举个栗子
比如有一个只能允许两人并行的小路,甲乙两人迎面相遇,他们都过于礼貌,走个迎面后,都往左一步让一步,又都往右让一步,当然可能一个给另一个人一个耳光,说,你丫是不是傻X啊!但是在这个耳光之前,他们是活锁状态,会一直这样让下去。
当然程序里也有这种例子,在这我就不举了,各位可以留言。
3.饥饿
所谓饥饿,就是一个或多个线程因为各种原因,长期得不到所需的资源,导致一直无法执行。
5.并发级别
并发级别:阻塞和非阻塞(非阻塞分为无障碍、无锁、无等待)
1.阻塞
当一个线程进入临界区时,其他的线程必须等待。
2.无障碍
特征
1)无障碍是最弱的非阻塞调度
2)线程可以***的进入临界区
3)在没有竞争时,有限步数完成操作退出
4)在有竞争时,回滚数据。
跟阻塞调度相比,阻塞调度是悲观的策略,它认为只要多个进程进行修改就会把数据改坏,所以一人得锁,其余等待。
而非阻塞调度呢,是一种乐观的策略,它认为多个进程未必会把数据改坏,所以每个线程都可以修改数据,但是当它发现数据冲突后,就会回滚这条数据。
3.无锁
与无障碍相比,无障碍调度会因为有冲突即回滚的原理,当并发量比较大的时候容易产生,每个线程调用都有冲突,每个线程都回滚,这种永远获取不到临界区的资源的情况。
而无锁呢,它增加了一个条件,保证每次竞争都有胜出者,这样就解决无障碍的问题。这样就能保证所有线程都能顺利执行下去。
无锁在Java中很常见
while (!atomicVar.compareAndSet(localVar, localVar+1)) {
localVar = atomicVar.get();
}
4.无等待
无等待是什么呢?
首先无等待的前提是在无锁的基础上的。无锁它只保证临界区有进有出,但是如果进的线程的优先级都很高,那么临界区低优先级的线程就可能一直处于饥饿状态。
无等待是并行的***别,他能使这个系统达到最优的状态。
无等待的典型案例
如果只有读没有写,那这个自然是无等待的。
如果既有读又有写,那么每个线程写之前都把数据拷贝一份,然后修改这个副本,而不是修改原始数据,所以没有冲突,那么这个修改也是无等待的。最后需要同步的只是写完之后覆盖源数据的操作。
由于无等待要求比较高,实现起来比较困难,所以多数我们会采用无锁。