问题详解
悲观锁和乐观锁
- 悲观锁:所以每次操作前都会上锁,其他线程阻塞。在 Java 语言中 synchronized 和 ReentrantLock 等就是典型的悲观锁,还有一些使用了 synchronized 关键字的容器类如 HashTable 等也是悲观锁的应用。适用于读少写多;
- 乐观锁:乐观锁操作数据时不会上锁,在更新的时候会判断一下在此期间是否有其他线程去更新这个数据。乐观锁可以使用版本号机制和 CAS 算法实现。在 Java 语言中 java.util.concurrent.atomic 包下的原子类就是使用 CAS 乐观锁实现的。适用于读多写少。
独占锁和共享锁
- 独占锁:是指锁一次只能被一个线程所持有。如果一个线程对数据加上排他锁后,那么其他线程不能再对该数据加任何类型的锁。获得独占锁的线程即能读数据又能修改数据。JDK 中的 synchronized 和 java.util.concurrent(JUC) 包中 Lock 的实现类就是独占锁。
- 共享锁:是指锁可被多个线程所持有。如果一个线程对数据加上共享锁后,那么其他线程只能对数据再加共享锁,不能加独占锁。获得共享锁的线程只能读数据,不能修改数据。在 JDK 中 ReentrantReadWriteLock 就是一种共享锁。
互斥锁和读写锁
- 互斥锁:是独占锁的一种常规实现,是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。
- 读写锁:是共享锁的一种具体实现,读写锁管理一组锁,一个是只读的锁,一个是写锁。ReentrantReadWriteLock 实现了 ReadWriteLock 接口。
公平锁和非公平锁
- 公平锁:是指多个线程按照申请锁的顺序来获取锁。
- 非公平锁:是指多个线程获取锁的顺序并不是按照申请锁的顺序。在 java 中 synchronized 关键字是非公平锁,ReentrantLock 默认也是非公平锁。
可重入锁
可重入锁:又称之为递归锁,是指同一个线程在外层方法获取了锁,在进入内层方法会自动获取锁。对于 Java ReentrantLock 而言, 他的名字就可以看出是一个可重入锁。对于 Synchronized 而言,也是一个可重入锁。
自旋锁
自旋锁:是指线程在没有获得锁时不是被直接挂起,而是执行一个忙循环,这个忙循环就是所谓的自旋。因此自旋锁是不适应锁占用时间长的并发情况的。
分段锁
分段锁:是一种锁的设计,并不是具体的一种锁。分段锁设计目的是将锁的粒度进一步细化,当操作不需要更新整个数组的时候,就仅仅针对数组中的一项进行加锁操作。在 Java 语言中 CurrentHashMap 底层就用了分段锁,使用Segment,就可以进行并发使用了。
锁升级(无锁|偏向锁|轻量级锁|重量级锁)
- 无锁:无锁状态其实就是上面讲的 乐观锁。
- 偏向锁:Java 偏向锁(Biased Locking) 是指它会偏向于第一个访问锁的线程,如果在运行过程中,只有一个线程访问加锁的资源,不存在多线程竞争的情况,那么线程是 不需要重复获取锁 的,这种情况下,就会给线程加一个偏向锁。
- 轻量级锁:当线程竞争变得比较激烈时,偏向锁就会升级为轻量级锁,轻量级锁认为虽然竞争是存在的,但是理想情况下竞争的程度很低,通过 自旋 方式等待上一个线程释放锁。
- 重量级锁:升级到重量级锁其实就是 互斥锁 了,一个线程拿到锁,其余线程都会处于阻塞等待状态。
锁优化技术(锁粗化、锁消除)
- 锁粗化:就是将多个同步块的数量减少,并将单个同步块的作用范围扩大,本质上就是将多次上锁、解锁的请求合并为一次同步请求。
- 锁消除:是指虚拟机编译器在运行时检测到了共享数据没有竞争的锁,从而将这些锁进行消除。