一、乐观锁与悲观锁
1.乐观锁:
乐观锁认为锁竞争是不激烈的,即是多个线程同时访问同一个共享变量冲突的概率是较小的,所以并不会真正地去加锁,而是直接尝试去访问数据。在访问数据的同时识别当前的数据是否已经出现访问冲突。
乐观锁的实现:可以引入一个版本号,借助版本号来识别当前的数据是否出现访问冲突。
2.悲观锁:
悲观锁认为锁竞争是比较激烈的,即是多个线程同时访问同一个共享变量冲突的概率是较大的,所以会在每次访问共享变量之前都去真正地加锁。
悲观锁的实现:先加锁,获取到锁再去操作数据,获取不到锁就等待。
Synchronized初始使用乐观锁策略,当发现锁竞争比较频繁的时候,就会自动切换成悲观锁策略。
二、读写锁
即是把读操作和写操作分别进行加锁。
读锁与读锁之间不互斥。(因为多个线程只是同时读数据,并没有线程安全问题,直接并发地读取即可)
读锁与写锁之间互斥,写锁与写锁之间也互斥。(因为多个线程同时,一个读数据,一个写数据,或者并发地写数据,都会产生线程安全问题)
读写锁特别适合“频繁读,不频繁写”的场景。
Java标准库提供了ReentrantReadWriteLock类实现读写锁,ReadLock是读锁,WriteLock是写锁。(其中写锁是只能被独占的,读锁是可以被共享的)
三、自旋锁与挂起等待锁
1.自旋锁:
如果获取锁失败,就立即再次尝试获取锁,无限循环,直到获取到锁为止。
2.挂起等待锁:
线程竞争获取锁失败后,就会进入阻塞状态,不占用CPU,直到操作系统调度之后才被唤醒。
两者相比较
自旋锁的优点是:没有放弃CPU资源,一旦锁被其它线程释放就能第一时间获取到锁,更高效,在锁持有时间比较短的场景下非常有用。
缺点是:如果锁被其它线程占用时间较长,那么自旋锁就会持续占有消耗CPU资源。
Synchronized中的轻量级锁策略大概率就是通过自旋锁实现的。
四、轻量级锁与重量级锁
锁的核心特性——原子性,是基于CPU硬件设备提供的。
a.CPU提供了原子操作指令。
b.操作系统基于CPU原子操作指令实现了mutex互斥锁。
c.JVM基于操作系统提供的互斥锁,实现了Synchronized和ReentrantLock等关键字和类。
1.轻量级锁
轻量级锁的加锁机制是尽可能不使用mutex,而是尽量在用户态代码完成,实在完成不了,再使用mutex。(所以只涉及少量的内核态和用户态切换,不太容易引发线程调度)
2.重量级锁
重量级锁的加锁机制重度依赖了操作系统提供的mutex。(所以涉及大量的内核态和用户态切换,容易引发线程调度)
Synchronized开始是一个轻量级锁,当锁冲突比较严重时,就会变成重量级锁。
五、公平锁与非公平锁
1.公平锁
按线程竞争锁的先后顺序来获取锁。
2.非公平锁
不遵守先后顺序,各个线程随机地获取锁。
Synchronized是非公平锁。
六、可重入锁与不可重入锁
1.可重入锁
允许同一个线程多次获取同一把锁。
2.不可重入锁
不允许同一个线程多次获取同一把锁。
Synchronized是可重入锁。
实现的方式是:在锁中记录持有该锁的线程身份以及一个计数器来记录该线程的加锁次数,如果发现当前尝试加锁的线程就是此时持有锁的线程,则计数器+1即可。