锁 的概念.md

1040

锁是多线程并发问题中的重要组成,本文简单介绍一下锁中的一些名词:

  • 乐观锁
  • 悲观锁

这两种是两种上锁的思想,乐观锁是假设每次操作都不会冲突,等到提交的时候在判断有没有冲突。其通常需要依赖一些操作系统提供的操作原语。具体到 Java,其核心在于 CAS 操作。乐观锁没有规定判断冲突后的操作,可以进行自旋,放弃,或升级成悲观锁。

悲观锁是很悲观,假设每次操作都可能发生冲突,因此每次操作前都对数据进行上锁。一般而言这种锁也是由操作系统系统的。

  • 可重入锁
  • 不可重入锁

可重入和不可重入一般是悲观锁的属性,表示一个线程加锁后,可不可以再次加锁。可重入锁一般要维护一个原子计数器记录上锁的次数,只有上锁次数为 0 时才可以释放锁。

  • 公平锁
  • 非公平锁

公平和非公平一般是悲观锁的属性,公平锁下,所有获取锁的线程采用先申请先获取的方式,所有线程申请锁时都直接加入队尾,然后队首线程获取锁。而非公平锁是所有线程获取锁时,会先尝试获取锁,获取失败后在加入队尾。

一般而言,在高并发下,非公平锁效率会高于公平锁,因为非公平锁会降低线程挂起的概率,后来线程有几率直接获取锁。并且非公平锁弹性较高,比如可以设置允许线程获取锁的时候自旋,一段时间后在进行挂起。

但非公平锁队列中的线程有可能会出现饿死的现象。

  • 独占锁
  • 共享锁

独占锁为每次只能有一个线程获取锁,共享锁为每次有多个对象能获取锁。独占锁因为只有一个,实现较为简单。共享锁下,除了朴素的规定最多 N 个线程,还有使用各种条件规则的读写锁等,因此较为复杂。

  • 自旋锁与适应性自旋

自旋锁简单的理解就是循环尝试获取锁,而不挂起线程,因为线程的挂起和恢复需要耗费额外的资源。当等待时间不长时,通过循环等待的方式可以提高效率。但自旋时间过长也会白白占用 CPU 资源,因此一般会采用适应性自旋,即通过自旋成功后提高允许自旋的最大时间,自旋失败后减少允许自旋的最大时间等算法动态确定自旋时间。

  • 偏向锁

这是 Java 虚拟机中的一种锁,但是在其它语境中可能有不同的意思,这里只取 JVM 中的含义。当该锁第一次被获取时,直接记录线程 ID,并对于该线程对该锁的操作,全部不做任何处理。当出现与该线程 ID 不同的线程访问时,再来判断此时第一个第一个线程是否持有锁(是否处于同步区),来决定是要撤销偏向锁还是重偏向。(此外,由于对象头中需要存储第一次计算的对象 Hash 值,因此当锁对象第一次计算出 Hash 值之后,将无法偏向)

  • 轻量级锁

这是 Java 虚拟机中的一种锁,但是在其它语境中可能有不同的意思,这里只取 JVM 中的含义。与之相对的为重锁,重锁就是采用操作系统的相关指令来对数据进行加锁。而轻量级锁是 Java 虚拟机的一种锁。具体做法为将对象头中的数据压入操作数栈,然后再对象头中存取锁对象的指针等信息,进入轻量级锁模式。当出现锁竞争时,待竞争的线程只能不断自旋,因为轻量级锁不具备阻塞的功能,只能用于标记该对象已上锁,当自旋到达一定程度时,会膨胀为重锁。

  • 重量级锁

Java 中使用操作系统暴露的接口对数据进行上锁,也需要将锁的相关信息存入对象头,因为使用到了操作系统的相关操作,因此可以阻塞线程,因为 Java 中使用的线程也是由操作系统提供的。

无锁,可偏向状态无锁,偏向锁,轻量级锁,重量级锁 之间的相互转化之后会单独进行说明