30.盘点各种各样的锁

作为本系列的最后一篇,我们今天来盘点一下前面这些章节中涉及的锁:

作为高并发的核心主题之一,各种各样的锁伴随我们整个课程 ,现在我们就来梳理一下到底有多少种锁。

需要注意的是,这些锁不是简单的包含与被包含的关系,也不是并列关系,而是从不同的角度看就有不同类型的锁。

1 悲观锁和乐观锁

我们接触的第一个锁是synchronized,其思想就是只要可能发生访问冲突的地方就先保护起来,因此这是一种悲观思想,即认为写多读少,遇到并发写的可能性高,每次去拿数据的时候都认为其他线程会修改,所以每次读写数据都会认为其他线程会修改,所以每次读写数据时都会上锁。其他线程想要读写这个数据时,会被这个线程block,直到这个线程释放锁然后其他线程获取到锁。这就是悲观锁synchronized修饰的方法和方法块、ReentrantLock,都是悲观锁。

但实际上很多时候是读多写少,而读的时候因为不改变原始文件,因此不存在访问冲突,如果此时仍然加锁再读,效率就太低了。这时候可以先读,如果发生了冲突再特别处理就行了。这就是乐观锁的思想,CAS就是典型的乐观锁。

举个防火的例子,悲观锁的策略是将一切可能发生火灾的地方全部防范。而乐观锁的策略是家里备着灭火器,平时该干嘛干嘛,哪里发生火灾就灭哪里。

2 自旋锁

自旋的意思就是当前线程一直在等待,对应的代码就是:

for(;;){
..执行代码..
}

在工程里写这种代码是非常危险的,可能导致程序进入死循环,但是在多线程里,我们大量看到类似的情况,这意味执行代码的条件要设计非常严谨,各种情况都要考虑周全。

另外,for循环不是一直执行的,否则会一直占有CPU资源,事实上,这里是根据CPU的时间片来按照一定的间隔执行的,因此在执行代码里经常会看到Unsafe类的方法。

自旋锁的优点: 避免了线程切换的开销。挂起线程和恢复线程的操作都需要转入内核态中完成,这些操作给Java虚拟机的并发性能带来了很大的压力。

自旋锁的缺点: 占用处理器的时间,如果占用的时间很长,会白白消耗处理器资源,而不会做任何有价值的工作,带来性能的浪费。因此自旋等待的时间必须有一定的限度,如果自旋超过了限定的次数仍然没有成功获得锁,就应当使用传统的方式去挂起线程。

自旋次数默认值:一般是10次,可以使用参数-XX:PreBlockSpin来自行更改。现在很多操作都使用自适应自旋来优化, 自适应意味着自旋的时间不再是固定的,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定的。有了自适应自旋,随着程序运行时间的增长及性能监控信息的不断完善,虚拟机对程序锁的状态预测就会越来越精准。

3 可重入锁

可重入锁(递归锁),指的是同一线程外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响。 在 JAVA 里 ReentrantLock 和 synchronized 都是可重入锁。

4 读锁和写锁

为了提高性能,Java 提供了读写锁,在读的地方使用读锁,在写的地方使用写锁,灵活控制。如果没有写锁的情况下,读是无阻塞的,在一定程度上提高了程序的执行效率。 读写锁分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥由 jvm 控制的,程序员只需要上好相应的锁要求代码只读数据,可以很多人同时读,但不能同时写,可上读锁代码修改数据,只能有一个人在写,且不能同时读取,那就上写锁Java 中 读 写 锁 有 个 接 口 java.util.concurrent.locks.ReadWriteLock , 也 有 具 体 的 实 现 ReentrantReadWriteLock。

5 独占锁和共享锁

独占锁又称为互斥锁、同步锁。

独占锁模式下,每次只能有一个线程能持有锁。独占锁是一种悲观保守的加锁策略,它避免了读/读冲突,如果某个只读线程获取锁,则其他读线程都只能等待,这种情况下就限制了不必要的并发性,因为读操作并不会影响数据的一致性。ReentrantLock 就是以独占方式实现的互斥锁。

共享锁 共享锁则允许多个线程同时获取锁,并发访问 共享资源,如:ReadWriteLock。共享锁则是一种乐观锁,它放宽了加锁策略,允许多个执行读操作的线程同时访问共享资源。java 的并发包中提供了 ReadWriteLock,读-写锁。它允许一个资源可以被多个读操作访问,或者被一个 写操作访问,但两者不能同时进行

6 锁状态

锁的状态总共有四种:无锁状态、偏向锁、轻量级锁和重量级锁。这个主要是针对synchronized优化而产生的。

重量级锁(Mutex Lock) 这种依赖于操作系统 Mutex Lock 所实现的锁我们称之为“重量级锁”。Synchronized 是通过对象内部的做监视器锁(monitor)实现。监视器锁是依赖于底层的操作系统的 Mutex Lock 来实现,而操作系统实现线程之间的切换这就需要从用户态转换到核心态,这个成本非常高,状态之间的转换需要相对比较长的时间,这就是为什么Synchronized 效率低的原因。

轻量级锁 轻量级”是相对于使用操作系统互斥量来实现的传统锁而言的。轻量级锁并不是用来代替重量级锁的,它的本意是在没有多线程竞争的前提下,减少传统的重量级锁使用产生的性能消耗。轻量级锁所适应的场景是线程交替执行同步块的情况,如果存在同一时间访问同一锁的情况,就会导致轻量级锁膨胀为重量级锁。

偏向锁 引入偏向锁是为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径,因为轻量级锁的获取及释放依赖多次 CAS 原子指令,而偏向锁只需要在置换ThreadID 的时候依赖一次 CAS 原子指令(由于一旦出现多线程竞争的情况就必须撤销偏向锁,所以偏向锁的撤销操作的性能损耗必须小于节省下来的 CAS 原子指令的性能消耗)。 轻量级锁是为了在线程交替执行同步块时提高性能,而偏向锁则是在只有一个线程执行同步块时进一步提高性能。

锁升级 == 随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级的重量级锁==(但是锁的升级是单向的, 也就是说只能从低到高升级,不会出现锁的降级)。

7 分段锁

分段锁主要针对ConcurrentHashMap的,为了兼顾性能和线程安全,在JDK7中,将ConcurrentHashMap分成若干独立的段,在JDK8中对ConcurrentHashMap做了很多优化,但是基本思想仍然是分段的。

8 死锁和活锁

synchronized同步锁,虽然能解决线程安全的问题,但是如果使用不当,就可能导致死锁,也即请求被阻塞而一直无法返回。

除了死锁,还有个活锁的情况,很多人只听过死锁,但是没听过活锁。

死锁: 一组互相竞争资源的线程因互相等待,导致“永久”阻塞的现象。死锁的四个条件缺一不可:互斥、占有且等待、不可抢占、循环等待。

活锁: 活锁指的是任务或者执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试—失败—尝试—失败的过程。也就是“生不如死”的状态,不过处于活锁的实体是在不断的改变状态,活锁有可能自行解开。

9 公平和非公平锁

公平锁 多个线程按照申请锁的顺序来获取锁。在并发环境中,每个线程会先查看此锁维护的等待队列,如果当前等待队列为空,则占有锁,如果等待队列不为空,则加入到等待队列的末尾,按照FIFO的原则从队列中拿到线程,然后占有锁。

非公平锁: 线程尝试获取锁,如果获取不到,则再采用公平锁的方式。多个线程获取锁的顺序,不是按照先到先得的顺序,有可能后申请锁的线程比先申请的线程优先获取锁。

非公平锁的优点: 非公平锁的性能高于公平锁。缺点: 有可能造成线程饥饿(某个线程很长一段时间获取不到锁)

Java中的非公平锁:synchronized是非公平锁,ReentrantLock通过构造函数指定该锁是公平的还是非公平的,默认是非公平的。

10 锁粗化和锁消除

这两种都是锁优化技术。

锁粗化: 如果一系列的连续操作都对同一个对象反复加锁和解锁,甚至加锁操作都是出现在循环体体之中,就算真的没有线程竞争,频繁地进行互斥同步操作将会导致不必要的性能损耗,所以就采取了一种方案:把加锁的范围扩展(粗化)到整个操作序列的外部,这样加锁解锁的频率就会大大降低,从而减少了性能损耗。

锁消除: 就是把锁干掉。当Java虚拟机运行时发现有些共享数据不会被线程竞争时就可以进行锁消除。那如何判断共享数据不会被线程竞争?

利用逃逸分析技术:分析对象的作用域,如果对象在A方法中定义后,被作为参数传递到B方法中,则称为方法逃逸;如果被其他线程访问,则称为线程逃逸。

在堆上的某个数据不会逃逸出去被其他线程访问到,就可以把它当作栈上数据对待,认为它是线程私有的,同步加锁就不需要了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

纵横千里,捭阖四方

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值