java高并发系列 - 第2天:并发级别

因为临界区的存在,多线程之间的并发必须受到控制。根据控制并发的策略,咱们能够把并发的级别分为阻塞无饥饿无障碍无锁无等待几种。java

阻塞

一个线程是阻塞的,那么在其余线程释放资源以前,当前线程没法继续执行。当咱们使用synchronized关键字或者重入锁时,咱们获得的就是阻塞的线程。数据库

synchronize关键字和重入锁都试图在执行后续代码前,获得临界区的锁,若是得不到,线程就会被挂起等待,直到占有了所需资源为止。安全

无饥饿(Starvation-Free)

若是线程之间是有优先级的,那么线程调度的时候老是会倾向于先知足高优先级的线程。也就是说,对于同一个资源的分配,是不公平的!图1.7中显示了非公平锁与公平锁两种状况(五角星表示高优先级线程)。对于非公平锁来讲,系统容许高优先级的线程插队。这样有可能致使低优先级线程产生饥饿。但若是锁是公平的,按照先来后到的规则,那么饥饿就不会产生,无论新来的线程优先级多高,要想得到资源,就必须乖乖排队,这样全部的线程都有机会执行。多线程

无障碍(Obstruction-Free)

无障碍是一种最弱的非阻塞调度。两个线程若是无障碍地执行,那么不会由于临界区的问题致使一方被挂起。换言之,你们均可以大摇大摆地进入临界区了。那么你们一块儿修改共享数据,把数据改坏了怎么办呢?对于无障碍的线程来讲,一旦检测到这种状况,它就会当即对本身所作的修改进行回滚,确保数据安全。但若是没有数据竞争发生,那么线程就能够顺利完成本身的工做,走出临界区。并发

若是说阻塞的控制方式是悲观策略,也就是说,系统认为两个线程之间颇有可能发生不幸的冲突,所以以保护共享数据为第一优先级,相对来讲,非阻塞的调度就是一种乐观的策略。它认为多个线程之间颇有可能不会发生冲突,或者说这种几率不大。所以你们都应该无障碍地执行,可是一旦检测到冲突,就应该进行回滚。高并发

从这个策略中也能够看到,无障碍的多线程程序并不必定能顺畅运行。由于当临界区中存在严重的冲突时,全部的线程可能都会不断地回滚本身的操做,而没有一个线程能够走出临界区。这种状况会影响系统的正常执行。因此,咱们可能会很是但愿在这一堆线程中,至少能够有一个线程可以在有限的时间内完成本身的操做,而退出临界区。至少这样能够保证系统不会在临界区中进行无限的等待。atom

一种可行的无障碍实现能够依赖一个"一致性标记"来实现。线程在操做以前,先读取并保存这个标记,在操做完成后,再次读取,检查这个标记是否被更改过,若是二者是一致的,则说明资源访问没有冲突。若是不一致,则说明资源可能在操做过程当中与其余线程冲突,须要重试操做。而任何对资源有修改操做的线程,在修改数据前,都须要更新这个一致性标记,表示数据再也不安全。线程

数据库中乐观锁,应该比较熟悉,表中须要一个字段version(版本号),每次更新数据version+1,更新的时候将版本号做为条件进行更新,根据更新影响的行数判断更新是否成功,伪代码以下:code

1.查询数据,此时版本号为w_v
2.打开事务
3.作一些业务操做
4.update t set version = version+1 where id = 记录id and version = w_v;//此行会返回影响的行数c
5.if(c>0){
        //提交事务
    }else{
        //回滚事务
    }

多个线程更新同一条数据的时候,数据库会对当前数据加锁,同一时刻只有一个线程能够执行更新语句。blog

无锁(Lock-Free)

无锁的并行都是无障碍的。在无锁的状况下,全部的线程都能尝试对临界区进行访问,但不一样的是,无锁的并发保证必然有一个线程可以在有限步内完成操做离开临界区。

在无锁的调用中,一个典型的特色是可能会包含一个无穷循环。在这个循环中,线程会不断尝试修改共享变量。若是没有冲突,修改为功,那么程序退出,不然继续尝试修改。但不管如何,无锁的并行总能保证有一个线程是能够胜出的,不至于全军覆没。至于临界区中竞争失败的线程,他们必须不断重试,直到本身获胜。若是运气很很差,老是尝试不成功,则会出现相似饥饿的先写,线程会中止。

下面就是一段无锁的示意代码,若是修改不成功,那么循环永远不会中止。

while(!atomicVar.compareAndSet(localVar, localVar+1)){
        localVal = atomicVar.get();
}

无等待

无锁只要求有一个线程能够在有限步内完成操做,而无等待则在无锁的基础上更进一步扩展。它要求全部线程都必须在有限步内完成,这样不会引发饥饿问题。若是限制这个步骤的上限,还能够进一步分解为有界无等待和线程数无关的无等待等几种,他们之间的区别只是对循环次数的限制不一样。

一种典型的无等待结果就是RCU(Read Copy Update)。它的基本思想是,对数据的读能够不加控制。所以,全部的读线程都是无等待的,它们既不会被锁定等待也不会引发任何冲突。但在写数据的时候,先获取原始数据的副本,接着只修改副本数据(这就是为何读能够不加控制),修改完成后,在合适的时机回写数据。

java高并发系列交流群

相关文章
相关标签/搜索