kim.zhang

风在前,无惧!


  • 首页

  • 标签42

  • 分类12

  • 归档94

  • 搜索

锁的分类.md

发表于 2020-08-10 更新于 2021-11-21 分类于 并发
本文字数: 2.8k 阅读时长 ≈ 3 分钟

1.锁的分类

img

2.悲观锁和乐观锁

  • 悲观锁(互斥同步锁)
    • 典型例子:Synchronized、lock
  • 乐观锁(非互斥同步锁)
    • 典型例子:原子类、并发容器
    • 使用CAS算法实现

3.悲观锁和乐观锁的开销对比

  • 悲观锁的原始开销要高于乐观锁,但是特点是一劳永逸,临界区持锁时间就算越来越长,也不会对互斥锁的开销造成影响
  • 乐观锁一开始的开销比悲观锁小,但是如果自旋时间很长或者不停重试,那么消耗的资源也会越来越多

4.使用场景

  • 悲观锁:适合并发写入多的情况,适用于临界区持锁时间比较长的情况,悲观锁可以避免大量的无用自旋等消耗
    • 临界区有IO操作
    • 临界区代码复杂或者循环量大
    • 临界区竞争非常激烈
  • 乐观锁:适合并发写入少,大部分是读取的场景,不加锁的能让读取性能大幅提升

5.可重入锁

  • 典型例子:Synchronized、ReentranceLock
  • getHoldCount() 获取可重入的次数

6.读锁插队策略和升降级

  • 公平锁
    • 不允许插队
  • 非公平锁
    • 写锁可以随时插队
    • 读锁仅在等待队列头节点不是想获取写锁线程的时候可以插队,否则可能会造成饥饿现象

升降级策略:只能降级,不能升级

栗子1:读锁插队

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
/**
* @Author Ming
* @Date 2020/07/26 19:12
* @Description 演示当队列中第一个不是写锁的时候,读锁是可以插队的
*/
public class ReentranceReadWriteLockTest {
// 非公平锁才能有插队
private final ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock(false);
private final ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
private final ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();

public void read() {
System.out.println(Thread.currentThread().getName() + "尝试获取读锁");
readLock.lock();
try {
System.out.println(Thread.currentThread().getName() + "获取到了读锁");
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + "释放读锁");
readLock.unlock();
}
}

public void write() {
System.out.println(Thread.currentThread().getName() + "尝试获取写锁");
writeLock.lock();
try {
System.out.println(Thread.currentThread().getName() + "获取到了写锁");
Thread.sleep(40);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + "释放写锁");
writeLock.unlock();
}
}

public static void main(String[] args) {
ReentranceReadWriteLockTest reentranceReadWriteLockTest = new ReentranceReadWriteLockTest();
new Thread(() -> reentranceReadWriteLockTest.write(), "thread-1").start();
new Thread(() -> reentranceReadWriteLockTest.read(), "thread-2").start();
new Thread(() -> reentranceReadWriteLockTest.read(), "thread-3").start();
new Thread(() -> reentranceReadWriteLockTest.write(), "thread-4").start();
new Thread(() -> {
Thread[] threads = new Thread[1000];
for (int i = 0; i < 1000; i++) {
threads[i] = new Thread(() -> reentranceReadWriteLockTest.read(), "子线程创建的Thread" + i);
}
for (int i = 0; i < threads.length; i++) {
threads[i].start();
}
}).start();
}
}

7.读写锁的适用场景

ReentranceReadWriteLock适用于读多写少的情况,合理使用可以进一步提高并发效率。

8.自旋锁

  • 阻塞或唤醒一个Java线程需要操作系统切换CPU状态来完成,这种状态转换需要耗费处理器时间
  • 如果同步代码快中的内容过于简单,状态转换消耗的时间很可能比用户代码执行的时间还要长
  • 在许多场景中,同步资源的锁定时间很短,为了这一小段时间去切换线程,线程挂起和恢复现场的花费可能会让系统得不偿失
  • 如果锁被占用的时间很长,那么自旋的线程只会白浪费处理资源
  • atomic包下的类基本都是自旋锁的实现
  • 自旋锁实现原理是CAS,AtomicInteger中调用unsafe进行自增操作的源码中的do-while循环就是一个自旋操作

9.自旋锁的适用场景

  • 自旋锁一般用于多核的服务器,在并发度不是特别高的情况下,比阻塞锁的效率高
  • 适用于临界区比较短小的情况,否则如果临界区很大(线程一旦拿到锁,很久以后才会释放),那也是不适合的

10.锁优化

  • 缩小同步代码块
  • 尽量不要锁住方法
  • 减少请求锁的次数
  • 锁中尽量不要再包含锁
  • 选择合适的锁类型或合适的工具类
一毛也是爱~
Kim.Zhang 微信支付

微信支付

# JUC
synchronized.md
线程池.md
  • 文章目录
  • 站点概览
Kim.Zhang

Kim.Zhang

且行且珍惜
94 日志
12 分类
42 标签
E-Mail Weibo
  1. 1. 1.锁的分类
  2. 2. 2.悲观锁和乐观锁
  3. 3. 3.悲观锁和乐观锁的开销对比
  4. 4. 4.使用场景
  5. 5. 5.可重入锁
  6. 6. 6.读锁插队策略和升降级
    1. 6.1. 栗子1:读锁插队
  7. 7. 7.读写锁的适用场景
  8. 8. 8.自旋锁
  9. 9. 9.自旋锁的适用场景
  10. 10. 10.锁优化
粵ICP备19091267号 © 2019 – 2022 Kim.Zhang | 629k | 9:32
本站总访问量 4 次 | 有 309 人看我的博客啦 |
博客全站共176.7k字
载入天数...载入时分秒...
0%