Java中的锁
2.1 Java中锁的分类
Java中锁的分类分成很多层概念。
2.1.1 悲观锁&乐观锁
Java中的悲观锁,比如synchronized,ReentrantLock,ReentrantReadWriteLock。
Java中的乐观锁,采用的CAS操作,CompareAndSwap(比较和交换),CAS是基于CPU原语实现的。
悲观锁: 悲观锁在获取不到锁资源后,会将当前线程挂起(BLOCKED,WAITING,TIMED_WAITING),线程挂起这个事情,不是JVM层面能解决的问题。需要操作系统来完成这个事情。那就需要涉及到用户态和内核态之间的切换,这种切换,会影响一定效率。
乐观锁: 乐观锁不涉及线程挂起,不涉及用户态和内核态之间的切换。如果长时间执行乐观锁的机制,但是一直无法成功,会浪费一些CPU性能,都在做尝试,但是一直没成功。
如果可以,最好是在尝试几次就能成功的场景,使用乐观锁实现。如果这个锁资源获取需要一定的时间,最好使用悲观锁。
2.1.2 可重入锁&不可重入锁
方法A内部加了sync锁,方法B内部也追加了sync锁。方法A和方法B中的sync锁是同一个对象。
同时方法A内部需要调用方法B
public class CompanyTest {
public static void main(String[] args) {
a();
}
public static synchronized void a(){ // CompanyTest.class
System.out.println("AA");
b();
}
private static synchronized void b() { // CompanyTest.class
System.out.println("BB");
}
}
这种情况,如果是可重入锁,自然可以在a方法内持有CompanyTest.class锁的同时,再去获取b方法需要获取的CompanyTest.class。
如果是不可重入锁,就会出现一个问题,CompanyTest.class锁已经内当前线程持有了,当前线程想再次获取CompanyTest.class锁,就无法获取,因为CompanyTest.class锁被当前线程持有。很容易出现死锁问题。
Java中常用的锁都是可重入锁: synchronized,ReentrantLock,ReentrantReadWriteLock都是可重入锁,他都有一个属性在记录锁重入的次数。
Java中也有不可重入锁: 不过他并不是来实现原子性的,他是线程池中的Worker对象。他是不可重入锁,或者说,他没实现重入锁机制。
2.1.3 公平锁&非公平锁
首先按照正常人类的理解:
- 公平锁:有素质的线程,排队,如果前面有排队的,线程就在后面等待。
- 非公平锁:没有素质的线程(也不是完全没素质),尝试插队,如果插队失败了,消停的后面排队。
在Java中,synchronized只支持非公平锁。
ReentrantLock和ReentrantReadWriteLock既支持公平锁也支持非公平锁,可以通过有参构造传递boolean的值类决定是否公平(默认非公平锁)。
// 默认不传递,fair是false,new的是NonfairSync,如果主动传递true作为参数,构建FairSync作为公平锁的实现
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
2.1.4 互斥锁&共享锁
其实互斥锁就是写锁,所有线程过来,必须先拿到锁资源才能操作,而且这个锁资源同一时间只能有一个线程持有。Java中的synchronized和ReentrantLock都是互斥锁。
如果这个业务需要加锁,但是这个业务是读多写少,所以这是就有了共享锁的概念。允许多个线程同一时间持有当前锁资源,同时还要保证如果有写操作,所有的读操作都不能持有锁。Java中的ReentrantReadWriteLock就是一个共享锁,或者说是一个读写锁。
互斥锁: 同一时间点,只能有一个线程持有当前的锁资源。
共享锁: 同一时间点,可以有多个线程同时只有当前的锁资源。