引言
JAVA的高并发设计一直都是当今互联网开发的重要环节,从今天开始我们逐步学习《Java高并发程序设计》,并将本书的重要内容总结陈述,希望与大家共同进步。
几个基本认知
-
同步(Synchronous)和异步(Asynchronous)
同步和异步通常表示一次方法的执行,同步表示方法二只能在方法一执行完毕后执行,异步表示方法一执行过程中,方法二可能已经执行了。 -
并发(Concurrency)和并行(Parallelism)
并行是指严格的同时执行。并发是多个任务内部交替执行,一会运行A,一会运行B,对于外部观察者来说,好像是同时执行的。 -
临界区
临界区表示一种公共资源或者共享数据,可以被多个线程使用。 -
阻塞(Bloking)和非阻塞(Non-Blocking)
阻塞和非阻塞用来形容多线程之间的相互影响。比如一个线程占用了临界区资源,另外一个线程就要等这个线程结束。等待会导致线程挂起,这种情况叫做阻塞。
非阻塞强调没有一个线程可以妨碍其他线程执行。 -
死锁(DeadLock),饥饿(Starvation),和活锁(LiveLock)
死锁是指多线程之间一直占用资源,不愿释放,导致一直等待,直到程序崩溃。
饥饿是指某个线程或者线程组因为种种原因无法获得所需要的资源,导致一直无法执行。
活锁是指线程主动释放资源给其他线程,但是其他线程也是主动释放,导致无法获取执行完毕的资源。
并发几个级别
- 阻塞(Blocking)
一个线程是阻塞的,那么在其他线程释放资源之前,当前线程无法执行。
(synchronized和重入锁) - 无饥饿(Starvation-Free)
线程之间具有优先级,如果使用非公平锁,可能导致低优先级的线程饥饿,所以使用公平锁可以防止饥饿。 - 无障碍(Obstruction-Free)
两个线程如果是无障碍的执行,就能够共享资源,共同修改资源,但是如果检测到有数据竞争,就会对自己的操作进行回滚。
无障碍的线程不一定是安全的,倘若产生极端情况,临界区资源被严重竞争,大家都会滚自己的操作,没有一个线程能够走出临界区。
一种无障碍的实现是依赖一个一致性标记来做的,线程在操作之前,都要提前读取并保存这个标记,操作之后再次读取,检查这个标记是否被更改过。如果不一致,重试操作。
并且,任何对临界资源修改的线程,在修改之前,都要进行标志的更新。 - 无锁(Lock-Free)
无锁也是无障碍的,与之不同的是,无锁的并发必须保证有一个线程能够在有限步完成操作离开临界区。 - 无等待(Wait-Free)
无等待是无锁的一个拓展,他要求所有线程必须在有限步内完成,离开临界区。
一种典型的无等待结构是RCU(Read-Copy-Update),基本思想是,对数据的读不做限制,写数据的时候,先取得原始数据副本,接着只修改副本数据,修改完成后,合适的时机回写数据。
并发的两个定律
将串行改为并发究竟能提高多少性能呢?主要有两个定律对这个问题进行解答
-
Amdahl
加速比定义: 加速比=优化前耗时/优化后耗时
n表示处理器个数,T表示时间,
T1表示优化前耗时(只有一个处理器的耗时),Tn表示使用n个处理器优化后的耗时。F是程序中只能串行执行的比例,那么(1-F)是并行执行的比例。
加速比= T1/Tn ;
Tn=T1(F+(1/n)/(1-f)) 带入上式得出:
T1/Tn = 1/(F+(1-F)/n)
我们可以发现,就算是n无穷大,加速比的大小完全在F的影响下。
即增加减少串行数量,适当增加处理器。 -
Gustafson
这部分请自行查阅书籍,这里不再赘述。
关于JMM
JMM指的是JAVA的内存模型。JMM关键技术点都是围绕着多线程的原子性,可见性和有序性建立的,我们首先讲一下这三个概念。
-
原子性(Atomicity)
原子性是指一个操作是不可以中断的。
举个栗子:一个变量i,某个线程对他操作记为1,另外一个线程对他操作记为-1,那么他要么是1,要么是-1,总是确定的,线程1和线程2之间是没有干扰的,这就是原子性的特点。
注:如果是long型的数据,在32位系统中会出问题,原因是long型是64位,线程执行时会丢失数据,导致数据串位。 -
可见性
可见性是指当一个线程修改了某个共享变量的值,其他线程能否立即知道这个修改。 -
有序性
有序性的原因是线程执行时,可能会产生指令重排 ,为什么会产生指令重排呢?
我们先来了解下一个指令的执行过程:
取值IF——>译码和取寄存器操作数ID——>执行或者有效地址计算EX——>存储器访问MEM——>写回WB
由于上边的每个步骤可能使用不同的硬件完成,因此攻城狮发明了流水线技术,流水线技术使得第一个指令用完某个硬件后立即交付,第二条指令如果用到交付额硬件立即可以使用。但这里有个问题是某个指令可能因为某些原因阻塞,这时难道让所有的指令进行等待么?显然是不合理的,应该把后边没有用到这个硬件的指令提前。这样就发生了指令重排。
那么,那些指令不能重排呢? -
不能重排的基本原则
(1)程序顺序原则:一个线程内保证语义的串行性
(2)volatile原则:volatile变量的写,先发生于读,这个保证了volatile的可见性。
(3)锁规则:解锁必然发生在加锁之前
(4)传递性:A先于B,B先于C,那么A先于C。
(5)线程的start()方法最先执行
(6)线程的所有操作先于线程的终结(Thread.join())
(7)对象的构造方法执行和结束先于finalize()方法
第一章总结结束,下一章我们来看一下JAVA并行程序基础
作者:select you from me
链接:https://mp.csdn.net/mdeditor/95772392
来源:CSDN
转载请联系作者获得授权并注明出处。