锁类型
:::primary
这里只会简单讲述每种锁的概念是什么,具体到锁的每种实现则会放到后面的部分再讲
:::
悲观锁和乐观锁
前言:悲观锁和乐观锁都是数据库并发控制中引入的概念,但并不仅限于数据库系统中使用
悲观锁
- 定义:
- [每次对共享变量的修改过程持悲观态度,认为其他线程访问会 频繁写入 共享变量]{.pink}
- [所以每次线程访问共享变量之前都会对其上锁,阻止其他线程访问 共享变量]{.pink}
- 核心:[共享变量每次都只能被一个线程访问,其余线程必须阻塞等待直到当前线程释放锁]{.red}
- 实现:[synchronized、ReentrantLock、ReentrantReadWriteLock(Java)]{.aqua}
- synchronized 底层是采用管程实现的,本质上就符合乐观锁的思想
- ReentrantLock 底层采用 CAS 机制实现,本质上是乐观锁,但是其思想符合的是悲观锁的思想
- 优点:[多数线程都对共享变量执行写操作时,能够通过阻塞线程避免线程无意义的空转,造成资源浪费]{.red}
- 缺点:
- [每个线程在执行结束之前都不会释放锁从而可能导致死锁问题的发生]{.green}
- [每个线程获取锁和释放锁的过程的过程会增加额外的开销]{.green}
- 细节:
- 定义:
乐观锁
定义:
- [每次对共享变量的修改过程持乐观态度,认为其他线程不会 频繁写入而只是读取 共享变量]{.pink}
- [所以每次线程访问共享变量时是不会上锁的,多个线程可以同时访问 共享变量]{.pink}
- [只有在多个线程同时写入共享变量时才会 检测是否发生数据冲突,如果发生冲突就会重新尝试更新数据,直到数据更新成功]{.pink}
核心:[多线程可以同时访问共享变量,如果线程行为发生冲突,那么就会不停重试直到成功为止]{.red}
实现:[CAS]{.aqua}
优点:
- [线程访问共享变量的过程不需要获取锁从而减少获取锁的开销,以及避免死锁问题]{.red}
- [底层借助硬件实现(指令集)而不是软件实现的,执行的效率也非常高]{.red}
缺点:
[乐观锁会造成 ABA 问题]{.green}
[线程冲突情况特别多时,每个线程重试的次数增多就会大幅增加开销,甚至超过上下文切换带来的开销]{.green}
:::warning
乐观锁的 ABA 问题带来的影响远不悲观锁带来的死锁问题影响大,不过确实是个缺点就是了
:::
细节:乐观锁机制是不会共享变量添加排他锁的,无论线程是读取还是写入变量,而是采用自旋的概念替代排他锁
:::info
① 悲观锁一定是独占锁吗?乐观锁是共享锁吗?
② 乐观锁会对共享变量上锁吗?
:::
公平锁和非公平锁
前提:公平或者非公平锁都是基于悲观锁而言的,乐观锁根本不存在锁机制也就无从谈起公不公平了
公平锁:
定义:
- [想要获取的锁的线程会先检查共享变量的锁是否被其他线程占用]{.pink}
- [如果锁没有被其他线程占用,那么线程获取该锁,如果锁已经被占用,那么直接进入阻塞队列等待]{.pink}
优点:[避免线程出现饥饿的现象:每个线程都能够在有限的时间内获取到锁的所有权]{.red}
缺点:[正在运行的线程释放锁之后,每次都需要从阻塞队列中唤醒线程,也就是执行上下文切换,增大开销]{.green}
图示
非公平锁
定义:
- [想要获取锁的线程会和 阻塞队列 中的队首线程竞争获取共享变量的锁]{.pink}
- [如果竞争锁成功,那么该线程就会直接获得锁,如果竞争失败,那么该线程就会进入阻塞队列等待]{.pink}
实现:synchronized 是非公平锁,ReentrantLock 默认是非公平锁,可以设置为非公平锁
优点:[非公平锁避免频繁唤醒阻塞的线程,减少线程上下文切换造成的开销,增大系统的吞吐量]{.red}
缺点:[容易造成 饥饿 现象:阻塞队列中的线程长时间获取不到锁的所有权]{.green}
图示
细节:
- [无论是公平锁还是非公平锁,阻塞队列中线程都是遵循先进先出的规则来获取锁的]{.red}
- [如果没有特别的要求,通常都会采用非公平锁以提升系统整体的性能]{.red}
独占锁和共享锁
- 独占锁:
- 定义:[共享变量的锁每次只能够由一个线程获取,其余线程只能够阻塞等待]{.pink}
- 实现:synchronized、ReetrantLock、ReentrantReadWriteLock.WriteLock
- 共享锁:
- 定义:[共享变量的锁每次可以由多个线程获取,但是多个线程只能够同时进行读操作]{.pink}
- 实现:ReentrantReadWriteLock.ReadLock
重入锁和不可重入锁
不可重入锁
- 定义:
- [如果线程已经获取到特定共享变量的锁后,想要再次获取该共享变量的锁]{.pink}
- [那么该线程将会因为自己持有锁而被阻塞,这样的锁就是不可重入的]{.pink}
- 细节:Java 默认提供的锁都是可重入的锁,可以采用 AQS 编写不可重入的锁
- 定义:
可重入锁:
定义:
- [如果线程已经获取到特定共享变量的锁后,想要再次获取该共享变量的锁]{.pink}
- [该线程可以继续获取该共享变量的锁而不是被自己阻塞,这样的锁称为可重入的]{.pink}
实现:synchronized、ReentrantLock
测试代码
// 第一次获取锁
private synchronized void method1(){
log.debug("第一次获取锁...");
// 调用方法第二次获取锁
method2();
}
// 第二次获取锁
private synchronized void method2(){
log.debug("第二次获取锁...");
method3();
}
// 第三次获取锁
private synchronized void method3()
{
log.debug("第三次获取锁...");
}