1. 本篇文章继续做小林笔记的笔记。

基本概念

  • 并发与并行
    • 并发:并发是多个任务好像在同时进行,但是实际上是一个个串行,只是因为人无法感知到这一过程
    • 并行:真正的两个任务同时在执行
  • 线程共享进程的哪些数据
    • 代码块
    • 数据块
    • 系统资源
  • 导致并发问题的三大因素

    • 原子性:一行代码可能分多条指令,多条指令不是一次性执行完毕
    • 可见性:A线程修改变量后。线程B没有立即发现
    • 指令重排序

      互斥和同步

      这里我就不用小林的话了,我使用周志明老师的话:互斥是实现同步的手段

    • 同步:两个或多个线程之间有默契,我要先做,你后做,或者是你要等我和你一起做等等。批次之间有沟通不是只顾自己的

    • 互斥:指某段代码在同一时间只能有一个线程执行。这段互斥代码也称临界区

      互斥和同步的实现方式

    • 信号量

      锁的两个分类

    • 忙等待锁:又称自旋锁,也就是当线程A尝试获取锁失败后,一致在这轮询尝试上锁,这种锁在单CPU上需要抢占式的调度器(使用时间片方式的)。因为它永远不会放弃CPU。

    • 无忙等待锁:获取锁失败后,加入等待队列,锁释放后再唤醒线程

      信号量

各种锁

在线程同步时我们会使用各种锁。今天具体了解一下以下几种锁:

  • 互斥锁
  • 自旋锁
  • 读写锁
  • 公平锁
  • 悲观锁
  • 乐观锁

    互斥锁和自旋锁

    这两种锁就是上面说过的无忙等锁和忙等锁
  • 互斥锁:加锁失败后,释放CPU,由操作内核实现
  • 自旋锁:加锁失败后,不释放CPU,一直轮询,不用切换内核态,通过CPU的CAS上锁

    如何选择?

  • 在短任务上使用自旋锁,因为如果使用互斥锁,上锁的工夫就可以执行完好几个这段任务了

读写锁

总结来说

  1. 加上读锁后,所有线程可以共同读
  2. 加上读锁后,写锁无法加
  3. 加上写锁后,读锁无法加
  4. 加上写锁后,只能有一个线程在写

问题:

  1. 有个线程想要写数据,但是当前有好多读数据的线程
  2. 某个线程刚读完释放锁,另一个读线程又进来了
  3. 最后造成写锁那个线程的饥饿,可以通过公平锁解决

    公平锁非公平锁

    总结来说:

  4. 线程A、B、C依次间隔时间去上锁

  5. 非公平锁:A执行完后,B/C同时在队列中,CPU有可能先执行C而忽略先来的B
  6. 公平锁:A执行完后,B、C同时在队列中,CPU一定会先执行B

乐观锁悲观锁

  • 乐观锁:乐观锁是一种无锁的机制,先改资源,成功就是赚到,不成功就再次修改知道成功为止
  • 悲观锁:在做事之前先加锁

锁分类

大家是否会对各种锁的名称感到不解呢,这里我以我目前的角度去整理一下。这些锁
多线程同步 - 图1

在我看来,任何锁都是基于互斥和自旋锁来实现的。而自旋锁和互斥锁各自可以分为公平加锁和非公平加锁之分。
读锁、写锁都是互斥锁和自旋锁之上实现的一种针对读写场景的两个锁实现。
至于悲观锁和乐观锁,这两种并不是锁的具体实现,而是程序员使用锁的方式。悲观锁就是在访问共享资源之前先加锁后操作。而乐观锁是直接尝试去修改,失败再尝试。虽然没加锁,但是可以达到加锁的性质

死锁

  1. 形成死锁的四个条件
    • 互斥条件:多个线程不可同时拥有这个资源
    • 持有并等待条件:线程已有一个资源,但是想申请另一个
    • 不可剥夺条件:线程自己使用完前不可释放
    • 环路等待:A等B释放1,B等A释放2

说白了当每个线程

  • 贪心 - 持有并等待
  • 自私 - 不可剥夺

就可能死锁。

  1. 解决死锁的方式
    • 一次性加锁
    • 顺序加锁

参考文章

小林 - 《图解系统》
周志明 -《深入理解JVM》