编程语言
首页 > 编程语言> > java 中的锁简介

java 中的锁简介

作者:互联网

一、Lock

简介

锁是一种工具,用于控制对共享资源的访问

Lock和Synchronized 它们都可以达到线程安全的目的,但是在使用上和功能上又有较大的不同

Lock并不是用来代替Synchronized的,而是当使用Synchronized不合适或不足以满足要求的时候来提供高级功能的

Lock 接口最常见的实现类是ReentrantLock

通常情况下,Lock值允许一个线程访问这个共享资源。不过有的时候,一些特殊的视线也可允许并发访问,比如ReadWriteLock里面的ReadLock

为什么需要Lock

Synchronized的弊端

Lock主要方法介绍

lock():

  1. 获取锁,如果锁已经被其他线程获取,则进行等待

  2. Lock不会想Synchronized一样在异常时自动释放锁,因此需要在finally中释放锁,以保发生异常时锁一定被释放。
  3. lock()方法不能被中断,一旦陷入死锁,lock()就会陷入永久等待

tryLock()

  1. 用来尝试获取锁,如果当前锁没有其他线程占用则获取成功(返回true),获取失败(返回false)
  2. 可以根据是否能获取到锁来决定后续程序行为
  3. 该方法会立即返回,即使在拿不到锁时不会一直在那等待
  4. tryLock(long time,TimeUnit unit):超时就放弃
  5. lockInterruptibly() : 相当于tryLock(long time,TimeUnit unit)  把超时时间设置为无限,在等锁的过程中,线程可以被中断

unlock()

  1. 解锁:一定要在fianlly中解锁

可见性

Lock的加解锁和synchronize有同样的语义,也就是说下一个线程加锁后可以看到所有前一个线程解锁前发生的所有操作(happen-before)

二、锁的分类

1. 乐观锁和悲观锁

悲观锁(互斥同步锁)的劣势

  1. 阻塞和唤醒带来的性能劣势
  2. 永久阻塞:如果持有锁的线程被永久阻塞例如遇到了无限循环。死锁等活跃性问题,那么等待该线程释放锁的将永远得不到执行
  3. 优先级反转

乐观锁

  1. 认为在自己处理操作的时候不会有其他线程来干扰,所以不会锁住被操作对象
  2. 在更新的时候,去对比咋我修改期间数据有没有被修改过,如果没有被修改过就正常去修改数据,如果有被修改过会选择放弃、重试等策略
  3. 乐观锁的视线一般都是利用CAS算法来实现的

悲观锁

  1. 如果不锁住这个资源,别人就会来争抢就会造成数据结果的错误,所以每次悲观锁为了确保结果的正确性会在每次获取并修改数据时把数据锁住,让别人无法访问该数据这样就可以确保数据内容万无一失
  2. java中的悲观锁的视线就是Synchronize和Lock相关类

悲观锁和乐观锁开销对比

  1. 悲观锁的原始开销要高于乐观锁但特点是一劳永逸,临界区持锁时间就算越来越长也不会对悲观锁的开销造成影响
  2. 乐观锁虽然一开始的开销被悲观锁小,但是如果自旋时间很长或者不停地重试,那么小号的资源也会越来越多
  3. 悲观锁适合并发写入多的情况,适用于临界区吃锁时间比较长的情况,悲观锁可以避免大量的无用自旋等消耗,典型情况

              临界区有IO操作

              临界区代码复杂或者循环量大

              临界区竞争非常激烈

  4. 乐观锁适合并发写入少,大部分是读取的场景,不加锁的能让读取性能大幅度提高

二、可重入锁和非重入锁 ReentrantLock

什么是可重入

同一个线程可以多次获取同一把锁

好处

  1. 避免死锁
  2. 提升封装性

源码

 

三、公平锁和非公平锁

公平:按照线程请求的顺序来分配锁

非公平:不完全按照请求的顺序,在一定情况下可以插队

注意:非公平同样不提倡“插队”行为,这里的非公平,指的是“在合适的时间”插队,而不是盲目插队,非公平可以提高效率,避免唤醒带来的空档期

公平的情况

不公平情况

如果在线程1释放锁的时候,线程5恰好去执行lock(),由于ReentrantLock 发现此时并没有线程持有lock这把锁(线程2还没来及获取到,因为获取需要时间)线程5可以插队直接拿到这把锁,这也是ReentrantLock默认的公平策略,也就是“不公平”。

特例

公平和非公平的优缺点

优势劣势
公平锁各线程公平等待,每个线程在等待一段时间后,总有执行机会                更慢,吞吐量更小
非公平锁更快,吞吐量更大有可能产生线程饥饿,也就是某些线程在长时间始终得不到执行

源码

 

四、共享锁和排它锁

共享锁和排它锁的典型是读写锁ReentrantReadWriteLock,其中读锁是共享锁,写锁是独享锁

读写锁作用

在读的地方使用读锁,在写的地方使用写锁,灵活控制,如果没有写锁的情况下,读是无阻塞的,提高了程序的执行效率,读写锁适用于读多写少的情况

读写锁的规则

要么是一个或多个线程同时有读锁,要么是一个线程有写锁,但是两者不会同时出现(要么多读,要么一写)

读锁插队策略

公平锁:不能插队

非公平锁:

读写锁的升降级

五、自旋锁和阻塞锁

自旋锁的缺点

  1. 如果锁占用时间很长,那么自旋的线程只会浪费处理器资源
  2. 在自旋过程中,一直消耗CPU,所以虽然自旋的其实开销低于悲观锁,但是随着自旋时间的增长,开销也是线性增长的。

适用场景

六、可中断锁

标签:java,写锁,简介,自旋,读锁,线程,公平,Lock
来源: https://blog.csdn.net/weixin_39477597/article/details/121046809