1. 公平锁和非公平锁
1.1 公平锁
指多个线程按照申请锁的顺序来获取锁,线程直接进入队列中排队,队列中的第一个线程才能获得锁。
优点:等待锁的线程不会饿死。
缺点:整体吞吐效率相对非公平锁要低,等待队列中除第一个线程以外的所有线程都会阻塞,CPU唤醒阻塞线程的开销比非公平锁大。
例子:
-
JDK中经过构造函数定义的ReentrantLock
new ReentrantLock(true)
1.2 非公平锁
多个线程加锁时,将直接尝试获取锁,获取不到才会到等待队列的队尾等待。但如果此时锁刚好可用,那么这个线程可以无需阻塞直接获取到锁。所以非公平锁有可能出现后申请锁的线程先获取锁的场景。
优点:可以减少唤起线程的开销,整体的吞吐效率高,因为线程有几率不阻塞直接获得锁,CPU不必唤醒所有线程。
缺点:处于等待队列中的线程可能会饿死,或者等很久才会获得锁。
例子:
-
JDK中的synchronized
-
JDK中默认的ReentrantLock
new ReentrantLock()
new ReentrantLock(false)
2. 独享锁和共享锁
2.1 独享锁/独占锁/排他锁(X锁)
指该锁一次只能被一个线程所持有。如果线程T对数据A加上排它锁后,则其他线程不能再对A加任何类型的锁。获得排它锁的线程即能读数据又能修改数据。
例子:
-
JDK中的synchronized和Lock的实现类
-
MySQL中的X锁
- 加了X锁的记录,不允许其他事务再加S锁或者X锁
2.2 共享锁(S锁)
指该锁可被多个线程所持有。如果线程T对数据A加上共享锁后,则其他线程只能对A再加共享锁,不能加独享锁。获得共享锁的线程只能读数据,不能修改数据。
例子:
-
MySQL中的S锁
- 加了S锁的记录,允许其他事务再加S锁,不允许其他事务再加X锁
3. 可重入锁和不可重入锁
3.1 可重入锁
广义上的可重入锁指的是可重复可递归调用的锁。在外层方法使用某一锁之后,在内层仍然可以使用,并且不发生死锁(前提得是同一个对象或者class),这样的锁就叫做可重入锁。
假设同一个类有A、B两个方法,且A、B都有被加上了同样的锁(比如都是this或是都是class),当A方法调用时,获得锁,在A方法的锁还没有被释放时,调用B方法时,B方法也获得该锁。即:当一个线程获得当前实例的锁lock,并且进入了被加上该锁的方法A,在方法A释放锁之前,可以再次进入隶属于同一个类的、被加上该锁的方法B。
注意:这种情景,既可以是不同的线程分别调用这个两个方法,也可是同一个线程,A方法中调用B方法,这个线程调用A方法。
例子:JDK中的synchronized和ReentrantLock
3.2 不可重入锁
与可重入锁相反,不可重入锁不可递归调用,递归调用就发生死锁。
一个类的A、B两个方法,A、B都有获得统一把锁,当A方法调用时,获得锁,在A方法的锁还没有被释放时,调用B方法时,B方法也获得不了该锁,必须等A方法释放掉这个锁。
当一个线程获得当前实例的锁lock,并且进入了方法A,在方法A释放锁之前,不可以再次进入方法B。
4. 乐观锁与悲观锁
4.1 乐观锁
乐观锁假定冲突的概率很低。
工作方式:先修改完共享资源,再验证这段时间内有没有发生冲突,如果没有其他线程在修改资源,那么操作完成,如果发现有其他线程已经修改过这个资源,就放弃本次操作。
场景:如果多线程同时修改共享资源的概率比较低,就可以采用乐观锁。
乐观锁全程并没有加锁,所以也叫无锁编程。
4.2 悲观锁
悲观锁做事比较悲观,它认为多线程同时修改共享资源的概率比较高
工作方式:访问共享资源前先要上锁
例子:自旋锁、互斥锁都是悲观锁。