Java并发编程——锁分类
Contents
按上锁方式划分
隐式锁:synchronized
- Java的关键字,是Java提供的同步机制
- 不需要显式的执行加锁和解锁过程
显式锁:JUC包中提供的锁
- JUC中提供的锁,加锁和解锁的方法都是显式的
按特性划分
乐观锁/悲观锁
- 按照线程在使用共享资源时,要不要锁住同步资源
- 悲观锁
- 在获取数据的时候会先加锁
- 实现:
JUC的锁、synchronized
- 乐观锁
- 在获取数据的时候不会加锁
- 在更新数据的时候会判断数据是否被其他线程修改过
- 实现:
CAS算法、JPA提供的@Version注解、关系型数据库的版本号机制
可重入锁/不可重入锁
- 按照同一个线程是否可以重复获取同一把锁
- 可重入锁
- 同一个线程可以重复获取同一把锁
- 实现:
synchronized、ReentrantLock
- 不可重入锁
- 重复获取会发生死锁
- 实现:
ReentrantReadWriteLock.WriteLock - 一般来说,应该尽量避免使用不可重入锁,除非确实有必要使用它来解决某些特殊问题
- 简化实现:在某些场景下,使用不可重入锁可以简化代码实现,因为不需要考虑同一线程多次获取锁的问题
- 限制资源访问:不可重入锁可以限制某些共享资源的访问,从而保证资源的独占性和线程安全性
- 防止死锁:不可重入锁可以防止同一线程出现死锁的情况
- 提高公平性:不可重入锁可以提高锁的公平性,因为同一线程无法多次获取锁,其他线程可以更容易地获得锁
公平锁/非公平锁
- 按照多个线程竞争同一锁时需不需要排队,能不能插队划分
- 公平锁
- 多个线程按照申请锁的顺序来获得锁
- 实现:
new ReentrantLock(true)
- 非公平锁
- 不是按照申请锁的顺序,允许“插队”
- 实现:
new ReentrantLock(false),synchronized - 非公平锁的性能一般会比公平锁的性能好,因为非公平锁减少了线程切换的次数
- 非公平锁的优势在于吞吐量比公平锁大
独享锁/共享锁
- 按照多个线程能不能同时共享同一个锁
- 独享锁(排他锁)(写锁)
- 一次只能有一个线程获取到锁
- 实现:
synchronized、ReentrantLock
- 共享锁(读锁)
- 一次可以有多个线程获取到锁
- 实现:
ReentrantReadWriteLock.ReadLock - 读锁可保证在读多写少的场景中,提高并发读的性能,增加程序的吞吐量
其他常见的锁
自旋锁
- 获取锁失败时,线程不会阻塞而是循环尝试获得锁,直至获得锁成功。
- 实现:
CAS,举例:AtomicInteger#getAndIncrement()
分段锁
- 实现:
ConcurrentHashMap - 可以理解为对独占锁的性能优化
无锁/偏向锁/轻量级锁/重量级锁
- 是synchronized独有的四种状态
- 不可降级,只能升级
Java已经提供了synchronized,为什么还要使用JUC的锁呢?
- synchronized同步锁的线程阻塞,存在有两个致命的缺陷:无法控制阻塞时长;阻塞不可中
断。
- 假如占有锁的线程被长时间阻塞(IO阻塞,sleep方法,join方法 等),由于线程在阻塞时没有释放锁,如果其他线程尝试获取锁,就会被阻塞只能一直等待下去, 甚至会发生死锁,这样就会造成大量线程的堆积,严重的影响服务器的性能。
- JUC的锁可以解决这两个缺陷:
tryLock(long time, TimeUnit unit)lockInterruptibly()
- 读多写少的场景,需要共享锁提高性能
- JUC的
ReentrantReadWriteLock.ReadLock
- JUC的