并发包-前言

从今天起开启一系列并发包的新线程,想要学习的童鞋,记得关注哦。

这篇文章讲解下,并发包的基本概念,为以后学习并发做铺垫,所以还是很重要的。废话少说,开撸。

目录

  • 同步和异步
  • 并发和并行
  • 临界区
  • 阻塞和非阻塞
  • 饥饿,死锁和活锁
  • 并发级别

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.无等待

无等待是什么呢?

首先无等待的前提是在无锁的基础上的。无锁它只保证临界区有进有出,但是如果进的线程的优先级都很高,那么临界区低优先级的线程就可能一直处于饥饿状态。

无等待是并行的*别,他能使这个系统达到最优的状态。

无等待的典型案例

如果只有读没有写,那这个自然是无等待的。

如果既有读又有写,那么每个线程写之前都把数据拷贝一份,然后修改这个副本,而不是修改原始数据,所以没有冲突,那么这个修改也是无等待的。最后需要同步的只是写完之后覆盖源数据的操作。

由于无等待要求比较高,实现起来比较困难,所以多数我们会采用无锁。