几种常见的锁锁的分类1.乐观锁和悲观锁2.公平锁和

锁的分类

在多线程的情况下,对共享变量的操作会出现线程安全的问题。使用互斥锁是一种最常见的策略,除了互斥锁,还有很多不同的锁。我们主要介绍以下几种常见的锁:

image.png

1. 乐观锁和悲观锁

1.1 乐观锁

  • 概念

乐观锁是一种乐观思想,在读取数据的时候认为别人不会修改,所以不上锁。只在写数据的时候,判断当前值是否与期望值相同,如果相同就进行更新。(更新操作为原子操作)。

  • 应用场景

1.2 悲观锁

悲观锁是一种悲观思想,认为遇到并发的场景比较多,每次取数据的时候都会有其他线程修改,所以每次在拿数据的时候都会进行加锁操作。因此其他线程都会被阻塞,直到当前线程执行完毕,释放锁之后,其他线程才可以竞争锁。保证了同一时刻只有一个线程可以进入临界区。

  • 应用场景

1.3 小结

乐观锁适合多读少写的场景,悲观锁适合读多写少的场景。

乐观锁的缺点:

  1. ABA问题

定义:如果线程1读取数据V的值为A,这时候另一个线程2也对V进行操作,将V的值先修改为B,再修改为A。这时候线程1发现V的值还是A,就认为V没有被修改。

ABA问题就是说张三的女朋友跟李四跑了,然后又和张三和好了,那他的女朋友还是先前的女朋友吗?(可怜的张三)

解决办法:使用版本号

  1. 循环时间开销很大

如果CAS长时间不能成功,就会给CPU带来很大的开销。

  1. 只能保证一个共享变量的原子操作

当对一个共享变量进行操作时,可以使用CAS来保证原子操作,但是对多个共享变量操作,CAS就无法保证操作的原子性。

2. 公平锁和非公平锁

2.1 公平锁

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

优点:不会被饿死

缺点:整体吞吐效率相较于非公平锁而言效率较低,等待队列中除第一个线程意外的所有线程都会阻塞,CPU唤醒阻塞线程的开销比非公平锁大。

2.2 非公平锁

公平锁是一种思想:多个线程获取锁的时候,会直接尝试获取锁,如果获取不到,就按照非公平锁的方式。多尔线程之间获取锁的方式不是按照先到先得的顺序。

优点:整体吞吐效率较高,因为线程获取锁的时候可能直接就获取到了,而不用唤醒所有的线程进行竞争。
缺点:等待队列中的线程可能会饿死,或等很久才能获取锁。

3. 可重入锁

可重入锁是一种思想:多个线程获取锁的时候,

4. 共享锁和独占锁

4.1 共享锁

可以被多个线程持有,以共享的方式持有锁。

当只有读操作的时候,锁在多个读线程之间是共享的。

image.png

4.2 独占锁

又叫排他锁,只能被一个线程持有,以独占的方式持有锁。JDK中的synchronized和JUC中的Lock就是互斥锁。

image.png

5. 读写锁

读写锁:读操作使用读锁,而写操作使用写锁。在没有写锁的情况下,读锁是无阻塞的,在一定程度上提高了执行效率。也就是说在同一时刻只允许一个写操作,而读操作之间互不影响。读写锁分为读锁和写锁,多个读锁不互斥,读锁和写锁互斥。读写锁在java中的实现为ReentrantReadWriteLock

6. 分段锁

ConcurrentHashMap: ConcurrentHashMap中被分为了多个segment,一共有16个segment。每次操作的时候都是先定位到相应的segment,再对该segment进行加锁操作,而不是对整个HashMap进行加锁操作。

image.png

参考文章:
juejin.cn/post/686792…