Java线程里的14种锁

 参考资料:

  1. 不可不说的Java“锁”事
  2. java多线程的15种锁

以下内容是参考上面两片文章写出的粗略总结, 想要细究可以看上面两位大佬写的文章, 由于是参考着写的, 所以有很多地方相同, 如果有侵权或不妥的地方还请联系删除.


一. 线程是否同步资源?

1. 悲观锁 : 同步

每次拿数据都按照 最坏 的情况来定, 认为一定会有别的线程过来修改, 所以每次拿数据之前都会先上锁, 这样别的线程要想来拿这个数据的时候就会被阻塞, 直到这个线程解锁. 

部分锁实现: 

synchronized 的实现就是悲观锁, 主要用于多写的场景, 可以确保数据同步正确

存在的部分问题: 

  • 线程持有该锁会导致其他需要此锁的线程阻塞
  • 竞争强烈的情况下, 不断的加锁和解锁都会影响cpu的调度从而出现性能问题
  • 如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置从而出现性能问题

2. 乐观锁 : 不同步

每次拿数据都按照 最好 的情况来定, 认为不会有别的线程过来修改, 所以每次拿数据之前都不会上锁, 但是会在更新数据的时候判断有没有别的线程去修改了这个数据.

部分锁实现: 

CAS算法, 主要用于多读的场景, 以此来提高数据的吞吐量

存在的部分问题: 

  • CAS只能保证单个变量操作的原子性,当涉及到多个变量时,CAS是无能为力的
  • 乐观锁在执行更新时频繁失败,就需要不断重试,从而浪费cpu资源
  • ABA问题

CAS (Compare And Swap) 比较和交换 : 用来保证数据操作的原子性

CAS包含了三个值: 

需要读写的内存值 V

进行比较的值 A

要写入的新值 B

一条线程想要对V修改为B, 那么在进程刚进入的时候就会让A=V, 然后在修改之前会进行判断, 如果此时的A==V, 那么就说明没有线程对V的值进行修改, 就会把B赋值给V, 如果出现了A!=V的情况, 就说明了有其他线程对V的值进行了修改, 那么就不会进行赋值, 而是会不断的自旋, 直到V==A.

但是CAS算法也存在一个非常明显的问题, 即:

ABA问题

ABA又是什么呢? 例如, 线程把数据从A修改为了B, 然后又把B修改成了A, 这样另一条线程读取的时候, 就会误认为数据一直是A, 没有变化, 从而忽略了过程.

但是ABA问题的解决方法也非常的简单: AtomicStampedReference

即在每次修改的时候添加一个版本号, 每次更新的时候都把版本号增加, 以此来解决ABA问题.

二. 同步资源失败, 是否要阻塞?

1. 阻塞

当一个线程在获取锁的时候, 如果该锁已被其他线程获取了, 那么该线程会切换至阻塞状态, 直到被唤醒.

2. 自旋锁 : 不阻塞

当一个线程在获取锁的时候, 如果该锁已被其他线程获取了, 那么该线程会进入一个循环自旋的状态, 不断尝试重新获取, 直到成功, 自旋锁的实现原理也来自CAS.

存在的部分问题:

  • 虽然避免了切换线程状态的开销, 但是它要占用cpu的时间, 如果长时间的自旋会白白浪费cpu的调度

3. 适应性自旋锁 : 不阻塞

适应性自旋锁是在jdk1.6之后引入的, 意味着自旋的次数将不再固定, 而是由上一次在该锁上的自旋时间和拥有者的状态共同决定的, 即如果有一个线程刚获得过该锁, 并且该线程还在运行中, 那么虚拟机就会认为这次自旋成功的概率也很大, 从而允许它自旋更长的时间, 反之如果一个某个锁, 很少被线程成功获得过, 那么对应的就会减少获取该锁线程的自旋时间甚至直接进入阻塞状态, 从而来避免cpu资源浪费.

三. 多个线程竞争同步资源的流程细节

这四种锁匙指锁的状态, 专门针对synchronized的

1. 无锁

没有锁住资源, 所有线程都能访问并修改同个资源, 但同一时间里只有一个能修改资源成功, 其他线程会循环重试, 直至修改成功.

CAS的原理就是无锁的实现, 无锁无法全面替换有锁, 但无锁在某些场合下的性能非常高.

2. 偏向锁

指一段同步代码一直被一个线程访问, 那么该线程会自动获取锁, 降低获取锁的代价.

大多数情况下, 锁总是由同一线程多次获得, 不存在多线程竞争, 所以出现了偏向锁, 其目的就是为了提高只有一条线程执行同步代码块的时候能够提高效率, 偏向锁只在遇到其他线程尝试竞争偏向锁的时候, 持有偏向锁的线程才会释放锁.

3. 轻量级锁

当偏向锁被另外的线程访问时, 就会升级为轻量级锁, 其他线程会通过自旋的形式来尝试获取锁, 不会阻塞, 从而提高性能.

4. 重量级锁

当轻量级锁的自旋超过一定次数, 或者又有第三个线程来拿锁的时候, 就会升级成重量级锁, 此时在等待锁的所有线程都会进入阻塞状态

四. 多个线程竞争时是否要排队

1. 公平锁 : 排队

指多个线程安装申请锁的顺序来获取锁, 线程直接进入队列中排队. 优点是等待锁的线程不会饿死, 缺点时整体吞吐效率相对非公平锁要低, 队列中除了第一个线程, 其他线程都会进入阻塞状态, CPU唤醒阻塞线程的开销比非公平锁大.

2. 不公平锁 : 不排队

指多个线程加锁时可以直接尝试获取该锁, 获取不到才会进入队列排队, 但如果刚好锁可用, 则会直接插队获取锁, 无须阻塞等待, 所以非公平可能会出现后申请先获得的情况. 非公平锁优点是可以减少唤醒线程的开销, 整体吞吐量高, 但是缺点也会导致等待队列中的线程要等待很久才会获得锁, 或者饿死.

公平锁和非公平锁的源代码中唯一的区别就在于公平锁在获取锁的时候多了一个限制条件: hasQueuedPredecessors(), 用于判断当前线程是否处于队列的第一个.

五. 一个线程中的多个流程能否获取同把锁

1. 可重入锁 : 能

指同一个线程在外层方法获取锁的时候, 再进入该线程的内层方法时, 会自动获取锁 (前提得是同一个锁对象), 不会因为外层已经获取过且还没有释放而导致阻塞.

部分锁实现:

ReentrantLock和synchronized都是可重入锁, 优点是可以一定程度上避免死锁的出现

2. 不可重入锁 : 不能

不可重入锁与之对立, 当同一个线程在外层获取到锁的时候, 再进入内层, 会导致内层一直在等待外层释放锁, 而外层又在等待内层执行, 无法释放锁, 从而出现死锁.

六. 多个线程能否共享一把锁

1. 共享锁 : 能

指当一个线程获取该锁后, 其他锁依旧可以对数据进行读取操作, 但是只有获取该锁的线程能对数据进行改写的操作

2. 互斥锁 : 不能

指当一个线程获取该锁后, 其他锁将不能对数据进行读写操作, 只有获取到该锁的线程可以对数据进行读写

以上的内容的是个人的一个总结, 水平有限, 可能不一定正确, 这篇文章仅对Java几个线程锁进行了一个初步的理解, 并没有深入探究, 如有错误还请多多包涵.

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

高傲的小煌人

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

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

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

打赏作者

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

抵扣说明:

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

余额充值