Java 并发-基础
几个并发概念的介绍
- 同步(synchronous)和异步(asynchronous)
同步调用会等待方法的返回,异步调用会马上返回,但是异步调用返回并不代表人任务已经完成,它会在后台启个线程继续进行任务
- 并发(Concurrency)和并行(Parallelism)
并发和并行在外在表象来说, 是差不多的. 由图所示, 并行则是两个任务同时进行, 而并发呢, 则是一会做一个任务一会又切换做另一个任务. 所以单个cpu是不能做并行的, 只能是并发.
- 临界区
临界区用来表示一种公共资源或者说是共享数据, 可以被多个线程使用, 但是每一次, 只能有一个线程使用它, 一旦临界区资源被占用, 其他线程要想使用这个资源, 就必须等待.
阻塞(Blocking)和非阻塞(Non-Blocking)
- 阻塞和非阻塞通常用来形容多线程间的相互影响. 比如一个线程占用了临界区资源, 那么其它所有需要 这个资源的线程就必须在这个临界区中进行等待, 等待会导致线程挂起. 这种情况就是阻塞. 此时, 如 果占用资源的线程一直不愿意释放资源, 那么其它所有阻塞在这个临界区上的线程都不能工作.
- 非阻塞允许多个线程同时进入临界区 所以阻塞的方式, 一般性能不会太好. 根据一般的统计, 如果一个线程在操作系统层面被挂起, 做了上下文切换了, 通常情况需要8W个时间周期来做这个事情.
死锁(Deadlock)、饥饿(Starvation)和活锁(Livelock)
- 死锁: 是指两个或两个以上的进程在执行过程中, 由于竞争资源或者由于彼此通信而造成的一种阻塞的现象, 若无外力作用, 它们都将无法推进下去. 此时称系统处于死锁状态或系统产生了死锁, 这些永远在互相等待的进程称为死锁进程.
- 但是死锁虽说是不好的现象, 但是它是一个静态的问题, 一旦发生死锁, 进程被卡死, cpu占有率也是0, 它不会占用cpu, 它会被调出去. 相对来说还是比较好发现和分析的.
- 活锁:指事物1可以使用资源, 但它让其他事物先使用资源;事物2可以使用资源, 但它也让其他事物先使用资源, 于是两者一直谦让, 都无法使用资源.
- 为了避免死锁把自己持有的资源都放弃掉. 如果另外一个线程也做了同样的事情, 他们需要相同的资源, 比如A持有a资源, B持有b资源, 放弃了资源以后, A又获得了b资源, B又获得了a资源, 如此反复, 则发生了活锁.
- 饥饿:指某一个或者多个线程因为种种原因无法获得所需要的资源, 导致一直无法执行.
- 死锁: 是指两个或两个以上的进程在执行过程中, 由于竞争资源或者由于彼此通信而造成的一种阻塞的现象, 若无外力作用, 它们都将无法推进下去. 此时称系统处于死锁状态或系统产生了死锁, 这些永远在互相等待的进程称为死锁进程.
并行的级别
- 阻塞 (当一个线程进入临界区后, 其他线程必须等待)
- 非阻塞
- 无障碍阻塞
- 无障碍阻塞是一种最弱的非阻塞调度
- 自由出入临界区
- 无竞争时,有限步内完成操作
- 有竞争时,回滚数据.
- 无锁
- 是无保障的
- 保证有一个线程可以胜出
- 无等待
- 无锁额
- 要求所有的线程都必须在有限步内完成
- 无饥饿的
- 无障碍阻塞
阻塞调度是一种悲观的策略, 它会认为说一起修改数据是很有可能把数据改坏的. 而非阻塞调度呢, 是一种乐观的策略, 它认为大家修改数据未必把数据改坏. 但是它是一种宽进严出的策略, 当它发现一个进程在临界区内发生了数据竞争, 产生了冲突, 那么无障碍的调度方式则会回滚这条数据.
在这个无障碍的调度方式当中, 所有的线程都相当于在拿去一个系统当前的一个快照. 他们一直会尝试拿去的快照是有效的为止. 无障碍并不保证有竞争时一定能完成操作, 因为如果它发现每次操作都会产生冲突, 那它则会不停地尝试. 如果临界区内的线程互相干扰, 则会导致所有的线程会卡死在临界区, 那么系统性能则会有很大的影响. 而无锁增加了一个新的条件, 保证每次竞争有一个线程可以胜出, 则解决了无障碍的问题. 至少保证了所有线程都顺利执行下去.
首先无等待的前提是无锁的基础上的, 无锁它只保证了临界区肯定有进也有出, 但是如果进的优先级都很高, 那么临界区内的某些优先级低的线程可能发生饥饿, 一直出不了临界区. 那么无等待解决了这个问题, 它保证所有的线程都必须在有限步内完成, 自然是无饥饿的.
无等待是并行的最高级别, 它能使这个系统达到最优状态.
无等待的典型案例: 如果只有读线程, 没有线线程, 那么这个则必然是无等待的. 如果既有读线程又有写线程, 而每个写线程之前, 都把数据拷贝一份副本, 然后修改这个副本, 而不是修改原始数据, 因为修改副本, 则没有冲突, 那么这个修改的过程也是无等待的. 最后需要做同步的只是将写完的数据覆盖原始数据.