[基础科目]--[锁]----锁分类mp.weixin.qq.com
前言
Java提供了丰富的锁种类,每个锁根据其特性可以在适当的场景中表现出非常高的效率。 本文旨在通过实例向读者介绍锁相关的源代码(本文的源代码来自JDK 8)、使用场景,介绍主流锁的知识点以及应用不同锁的场景。
锁的分类
Java中出现的几个锁的基本概念如图所示
乐观摇滚和悲观摇滚
乐观锁和悲观锁是广义的概念,体现了看线程同步的不同角度。在Java和数据库中都有与这个概念对应的实用。
悲观锁:对于同一数据的并发操作,悲观锁认为自己使用数据时一定会有另一个线程修改数据,所以在获取数据时先锁定,避免其他线程修改数据。 在Java中,同步关键字和Lock的实现类是悲观锁定。
乐观锁:认为其他线程在使用数据时不会修改数据,所以不添加锁,只是在更新数据时确定其他线程是否更新了数据。 如果数据没有更新,当前线程将成功写入自己修改的数据。 如果其他线程正在更新数据,请根据实现方法执行不同的操作(例如报告错误或自动重试)。
乐观锁定在Java中通过使用无锁定编程来实现,最常用的是CAS算法,在Java原子类中的增量操作通过CAS自旋来实现。
从上述概念说明可以看出,以下内容。
悲观锁定适用于写入操作较多的场景,先锁定可以保证写入操作时的数据正确。
乐观锁适用于读取操作较多的场景,不上锁的特点可以大幅度提高读取操作的性能。
虽然仅从概念上来说是抽象的,但是让我们看一下如何调用乐观锁和悲观锁的示例:
调用方法示例表明,悲观锁基本上先显式锁定,然后再操作同步资源,而乐观锁直接操作同步资源。
自旋锁定VS自适应自旋锁定
在介绍自旋锁之前,为了理解自旋锁的概念,需要介绍一些前提知识。 您知道,要阻止或唤醒Java线程,操作系统必须在CPU状态之间切换才能完成,这种切换需要花费一些处理器时间。 如果同步代码块的内容过于简单,则状态转换可能比用户代码的执行时间长。 此时,为了优化该消耗,引入了自旋锁。当存在具有锁的线程时,不是其他线程直接挂起,而是等一下,等待上一个线程的执行结束,从而实现线程切换的额外开销
从定义中也可以看出,自旋锁有缺陷,如果待机的线程长时间不解除锁定,自选待机的效果就会变低,比线程切换更费时间。 因此,需要添加限制旋转次数的阈值。 将默认值更改为10,-XX:PreBlockSpin。 数量到达后就不再旋转,挂起线程。
自旋锁的实现原理也同样是CAS,在AtomicInteger中调用unsafe进行自增量操作的源代码do-while循环是一种自旋操作,如果数值修正失败,循环将执行自旋直到修正成功
旋转锁在JDK1.4.2中引入,并使用-XX: UseSpinning打开。 在JDK 6中默认启用,引入了自适应自旋锁定(自适应自旋锁定)。
适应意味着自旋的时间(次数)不再固定,是由上次花在同一把钥匙上的自旋时间和钥匙所有者的状态决定的。 如果旋转等待刚刚在同一锁定对象上被锁定,且具有锁定的线程正在运行,则虚拟机可以认为这次旋转很可能再次成功,并且旋转等待可以持续相对较长的时间。 如果对于特定的锁很少可能成功获取自旋,则在稍后尝试获取该锁时可以省略自旋过程,而不直接阻塞线程从而浪费处理器资源。
自旋锁还有其他三种常见的锁定形式:TicketLock、CLHlock和MCSlock。 文中只作名词介绍,不作深入说明。 感兴趣的学生可以自己查阅相关资料。
公平锁定和非公平锁定
公平锁:的多个线程按照申请锁的顺序获得锁。
异步锁:以非顺序取得锁,多个线程取得锁顺序不是申请锁的顺序、之后有可能申请的先取得锁、优先顺序反转、或引起饥饿现象.
如图所示,打水人排队从管理者处取得打水资格后打水,与行列一样先出后顺序执行。 谁先拿钥匙,公平锁门。
如图所示,打水的人排队从管理员那里取得打水的资格,然后可以插队拿钥匙。 是不公平的关键。
可重入锁和非可重入锁
可重构锁:也称为递归锁,可以在多个层获得锁。 当外层申请锁后,内层仍然可以使用锁且不发生锁(前提是它们是同一对象或类)的锁称为可配置锁。 例如,返回锁定、同步等
图为可以突入锁定同步的使用
可重构锁:与可重构锁相反,不能进行递归调用,在递归调用中会发生死锁。
独占锁和共享锁
简朴的导师和共享锁是一个概念,类似于悲观锁和乐观锁。 垄断锁:也称为列
他锁,该锁每一次只能有一个线程持有。比如当数据A被线程T加锁后,其他线程不可用再对数据A加任何其他的锁.只有获取该排它锁的线程可以对数据进行查看和修改.JDK中的synchronized和Lock实现类都是独占锁.共享锁:该锁可以被多个线程共有.如果线程T对数据A加上共享锁后,则其他线程只能对A再加共享锁,不能加排它锁。获得共享锁的线程只能读数据,不能修改数据.
俭朴的导师与共享锁也是通过AQS来实现的,通过实现不同的方法,来实现独享或者共享.
分段锁
分段锁 其实是一种锁的设计思想,他并不是一种锁.典型的实现就是ConcurrentHashMap,其并发操作就是使用分段锁来设计实现的以达到高效的并发操作.
结语
本文Java中常用的锁以及常见的锁的概念进行了基本介绍,接下来的章节小哈会对照JDK的源码实现来详解介绍各个类型锁的实现和原理.
其实Java本身已经对锁本身进行了良好的封装,降低了研发同学在平时工作中的使用难度。但是研发同学也需要熟悉锁的底层原理,不同场景下选择最适合的锁。而且源码中的思路都是非常好的思路,也是值得大家去学习和借鉴的。