同步与异步
同步和异步的概念对于很多人来说是一个模糊的概念。其实我们的生活中存在着很多同步异步的例子。比如:你叫我去吃饭,我听到了就立刻和你去吃饭,如果我们有听到,你就会一直叫我,直到我听见和你一起去吃饭,这个过程叫同步;异步过程指你叫我去吃饭,然后你就去吃饭了,而不管我是否和你一起去吃饭。
-
同步交互:指发送一个请求,需要等待返回,然后才能够发送下一个请求,有个等待过程;
-
异步交互:指发送一个请求,不需要等待返回,随时可以再发送下一个请求,即不需要等待。
线程与进程
进程就是一个应用程序在处理机上的一次执行过程,它是一个动态的概念,而线程是进程中的一部分,进程包含多个线程在运行。如下是网上看到的一个很好的例子
线程与进程个人的理解好比地铁5号线中的一条线路的阻塞会影响整段5号线的运行。间接的也会影响到其他地铁线路的阻塞。进程好比站点(国贸站,大望路站等),线程好比线路一个站点可以有多条线路,站点不是活动的实体是没有能力调配整套地铁资源的。只有中央系统才能统一操作活动的实体。
进程作为地点基本单位,线程作为独立运行和路线调度的基本单位,不需要拥有资源(如厕所,空调,美化等),所以付出的开销就比较小,多个线路的的通行能更高效提高系统。每条线路有自己设计空间和目标站点就也是所说执行上下文。子进程和父进程有不同的车站(5号线和1号线东单站),而多个线路可共享同一个站点。
并行与并发
-
并发:实质是一个物理CPU(也可以多个物理CPU) 在若干道程序之间多路复用,并发性是对有限物理资源强制行使多用户共享以提高效率。
- 并行:性指两个或两个以上事件或活动在同一时刻发生。在多道程序环境下,并行性使多个程序同一时刻可在不同CPU上同时执行。
并发,是在同一个cpu上同时(不是真正的同时,而是看来是同时,因为cpu要在多个程序间切换)运行多个程序。
并行,是每个cpu运行一个程序。
打个比方。并发,就像一个人(cpu)喂2个孩子(程序),轮换着每人喂一口,表面上两个孩子都在吃饭。并行,就是2个人喂2个孩子,两个孩子也同时在吃饭。
临界区
临界区(Critical Section)是一段供线程独占式访问的代码,也就是说若有一线程正在访问该代码段,其它线程想要访问,只能等待当前线程离开该代码段方可进入,这样保证了线程安全。他工作于用户级(相对于内核级),在Window系统中CRITICAL_SECTION实现临界区相关机制。
阻塞与非阻塞
阻塞与非阻塞通常来形容多线程间的相互影响。比如一个线程占用了临界区资源,那么其他所有需要这个资源的线程就必须在这个临界区中进行等待。等待会导致线程挂起,这种情况就是阻塞,此时,如果占用资源的线程一直不愿意释放资源,那么其他所有阻塞在这个临界区的线程都不能正常工作。
非阻塞与值相反,他强调没有一i个县城可以妨碍其他线程执行,所有的线程都会尝试不断向前执行,接下来会详细描述。
死锁、饥饿和活锁
死锁、饥饿和活锁都属于多线程的活跃性问题。如果发生以上几种情况,那么线程可能很难在继续往下执行了。
死锁
死锁是最糟糕的一种情况,指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。由于资源占用是互斥的,当某个进程提出申请资源后,使得有关进程在无外力协助下,永远分配不到必需的资源而无法继续运行,这就产生了一种特殊现象死锁。
虽然进程在运行过程中,可能发生死锁,但死锁的发生也必须具备一定的条件,死锁的发生必须具备以下四个必要条件。
- 互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。
- 请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
- 不剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
- 环路等待条件:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。
饥饿
饥饿是指某一个或多个线程因为种种原因无法获得所需要的资源,导致一直无法执行。比如它的线程优先级可能太低,而高优先级线程不断抢占它所需的资源,导致低优先级线程无法工作。还有一种情况就是某一线程一直站着关键资源不放,导致其他需要这个资源的县城无法正常执行,这种情况也是饥饿的一种。与死锁相比,饥饿还是有可能在未来的一段事件内解决的。
活锁
活锁指事物1可以使用资源,但它让其他事物先使用资源;事物2可以使用资源,但它也让其他事物先使用资源,于是两者一直谦让,都无法使用资源。
并发级别
由于临界区的存在,多线程之间的并发必须受到控制。根据控制并发的策略,我们可以把并发的级别进行分类,大致上可以分为阻塞、无饥饿、无障碍、死锁、无等待几种。
阻塞
一个县城是阻塞的,那么在其他线程释放之前,当前线程无法继续工作。当我们使用synchronized关键字,或者重入锁时我们得到的就是阻塞的线程。
无论是synchronized或者重入锁,都会试图在执行后续代码前,得到临界区的锁,如果得不到,线程就会被挂起等待,直到占用了所需资源为止。
无饥饿
如果线程之前是有优先级的,那么线程调度的时候总是会倾向于高优先级线程。也就是说同一资源的分配是不公平的。对于非公平的锁来说,系统允许高优先级的县城插队。这样有可能导致低优先级线程产生饥饿。但如果锁是公平的,满足先来后到的原则,那么饥饿就不会产生,不管新来的线程优先级多高,要想获得资源,就必须乖乖排队。那么所有的线程都有机会执行。
无障碍
无障碍是一种最弱的非阻塞调度。两个线程如果是无障碍的执行,那么他们不会因为临界区的问题导致一方被挂起。大家都可以大摇大摆进入临界区工作。那么如果大家都修改了共享数据怎么办呢?对于无障碍的线程来说,一旦出现这种情况,当前线程就会立即对修改的数据进行回滚,确保数据安全。但如果没有数据竞争发生,那么线程就可以顺利完成自己的工作,走出临界区。
如果阻塞控制的方式比喻成悲观策略。也就是说系统认为两个线程之间很有可能发生不幸的冲突,因此,保护共享数据为第一优先级。相对来说,非阻塞的调度就是一种乐观策略,他认为多线程之间很有可能不会发生冲突,或者说这种概率不大,但是一旦检测到冲突,就应该回滚。 从这个策略来看,无障碍的多线程程序不一定能顺利执行。因为当临界区的字眼存在严重的冲突时,所有线程可能都进行回滚操作,导致没有一个线程可以走出临界区。所以我们希望在这一堆线程中,至少可以有一个线程可以在有限时间内完成自己的操作,至少这可以保证系统不会再临界区进行无线等待。 一种可行的无障碍实现可以依赖一个“一致性标记”来实现。线程在操作之前,先读取并保持这个标记,在操作完后,再次读取,检查这个标记是否被修改过,如果前后一致,则说明资源访问没有冲突。如果不一致,则说明资源可能在操作过程中与其他写线程冲突,需要重试操作。任何对保护资源修改之前,都必须更新这个一致性标记,表示数据不安全。无锁
无锁的并行都是无障碍的。在无锁的情况下,所有的线程都能尝试对临界区的资源进行访问,但不同的是,无锁的并发保证必然有一个线程能够在有限步内完成操作离开临界区。
在无锁的调度中,一个典型的特点是可能会包含一个无穷循环。在这个循环中线性不断尝试修改共享数据。如果没有冲突,修改成功,那么线程退出,否则尝试重新修改。但无论如何,无锁的并行总能保证有一个线程可以胜出,不至于全军覆没。至于临界区中竞争失败的线程,则不断重试。如果运气不好,总是不成功,则会出现类似饥饿的现象,线程会停止不前。无等待
无锁是要求至少有一个线程在有限步内完成操作,而无等待则是在无锁的基础之上进一步扩展。他要求所有线程都必须在有限步内完成操作。这样就不会引起饥饿问题。如果限制这个步骤上限,还可以分为有界无等待和线程无关的无等待几种,它们之间的区别只是对循环次数的限制不同。
一种典型的无等待结构是RCU(read-copy-update)。它的基本思想是,对数据的读可以不加控制,因此所有读线程都是无等待的,它们既不会被锁定等待也不会引起任何冲突。但在写数据时,先取得原始数据的副本,接着只修改副本数据,修改完后,在合适的时机回写数据。线程状态
要获取状态可以通过线程(Thread)的getState()来获取状态的值。例如,获取当前线程的状态就可以使用Thread.currentThrad().getState()来取值。该方法返回的类型是一个枚举类型,包含了new、runnable、blocked、waiting、timed_waiting、terminated这些值。
public enum State { NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED;}
- new:状态表示刚刚创建的线程,这种线程还没有执行。等到线程的start()方法调用时,才表示线程开始执行。当线程执行时,处于runnable状态.
- runnable:线程所需的一切资源都已经转杯好了。如果线程在执行中遇到了synchronized同步块,就会进入blocked阻塞状态,
- blocked:此时的状态为线程会暂停执行,直到获得请求的锁。
- waiting:进入一个无时间限制的等待。等待特殊事件,如通过wait()方法等待的线程在等待notify方法,而通过join()方法等待的线程则会等待目标线程终止。一旦等到了期望的事件,线程就会再次执行,进入runnable状态。
- timed_waiting:进入一个有时间限制的等待。等待特殊事件,如通过wait()方法等待的线程在等待notify方法,而通过join()方法等待的线程则会等待目标线程终止。一旦等到了期望的事件,线程就会再次执行,进入runnable状态。
- terminated:当线程执行完毕后,则进入terminated状态,表示结束。