并发学习笔记
同步(Synchronous)和异步(Asynchronous)
同步:方法调用一旦开始,调用者必须等到方法调用返回后,才能继续后续的行为。
异步:方法调用更像是一个消息的传递,一旦开始,方法调用就会立即返回,调用者可以继续后续的操作。而异步方法通常会在另一个线程中“真实”地执行。
并发(Concurrency)和并行(Parallelism)
它们都可以表示两个或者多个任务一起执行,但是偏重点有些不同。并发偏重于多个任务交替进行,而多个任务之间还有可能是串行的。而并行是真正意义上的“同时执行”。
严格意义来说,并行的多个任务是真实同时执行的。但是对于并发来说,这个过程只是交替的,一会儿运行任务A一会儿执行任务B,系统会不停地在两者间切换。但是对于外部观察者来说,即使多个任务之间是串行并发的,也会造成多任务间是并行执行的错觉。
- 解释一:并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔发生。
- 解释二:并行是在不同实体上的多个事件,并发是在同一实体上的多个事件
- 解释三:在一台处理器上“同时”处理多个任务,在多台处理器上同时处理多个任务。如hadoop分布式集群
临界区
临界区用来表示一种公共资源或者说是共享数据,可以被多个线程使用。但是每一次,只能有一个线程使用它,一旦临界资源被占用,其他线程想要使用这个资源,就必须等待。
在并行程序中,临界区资源是保护的对象,如果意外出现打印机同时执行两个打印任务,那么最可能的结果就是打印出来的文件会是损坏的文件。
阻塞(Blocking)和非阻塞(Non-Blocking)
阻塞和非阻塞通常用来形容多线程间的互相影响。比如一个线程占用了临界区资源,那么其他所有需要这个资源的线程就必须在这个临界区中进行等待。等待的线程会挂起,这种情况就是阻塞。
非阻塞的意思与之相反,它强调没有一个线程可以妨碍其他线程执行。所有的线程都会尝试不断前向执行。
死锁(Deadlock)、饥饿(Starvation)和活锁(Livelock)
死锁是两个或更多线程阻塞着等待其它处于死锁状态的线程所持有的锁。死锁通常发生在多个线程同时但以不同的顺序请求同一组锁的时候。
例如,如果线程1锁住了A,然后尝试对B进行加锁,同时线程2已经锁住了B,接着尝试对A进行加锁,这时死锁就发生了。线程1永远得不到B,线程2也永远得不到A,并且它们永远也不会知道发生了这样的事情。为了得到彼此的对象(A和B),它们将永远阻塞下去。这种情况就是一个死锁。
饥饿是指某一个或者多个线程因为种种原因无法获取所需要的资源,导致一直无法执行,比如它的优先级可能太低,而高优先级的不断抢占它所需要的资源,导致低优先级的线程无法工作。另一种可能是,某一个线程一直占着关键资源不放,导致其他需要这个资源的线程无法正常执行,这种情况也是饥饿的一种。与死锁相比,饥饿还是有可能在未来一段时间内解决的(比如高优先级的线程已经完成任务,不再疯狂的抢占资源)
活锁是任务或者执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试,失败,尝试,失败。 活锁和死锁的区别在于,处于活锁的实体是在不断的改变状态,所谓的“活”, 而处于死锁的实体表现为等待;活锁有可能自行解开,死锁则不能。这是一种非常有趣的情况。类似于平时我们坐电梯,要进的时候,有一个人和我同时进入,显然我们都无法进入,于是我选择礼让,但很巧,对面也选择礼让,于是就出现了活锁。
并发级别
由于临界资源的存在,多线程之间的并发必须要受到控制。根据控制并发的策略。我们可以把并发的级别进行分类,大致上可以分为:阻塞、无饥饿、无障碍、无锁、无等待几种。
阻塞
一个线程是阻塞的,那么在其他线程释放资源之前,当前线程无法继续执行。
无饥饿
如果线程之间是有优先级的,那么线程调度的时候会倾向于满足高优先级的线程。也就是说,对于同一个资源的分配,是不公平的。但是如果锁是公平的,满足先来后到,那么饥饿就不会产生,不管新来的线程优先级多高,要想获取资源,就必须乖乖排队。那么所有的线程都有机会执行。