JAVA锁的工具类 - 两种基本方式

锁接口和类

2020年12月11日

13:58

目录 - - 1. synchronized的不足 - 2. JDK中的锁实现 - 2.1 AQS - 2.1.1 使用 - 2.1.2 原理 - 2.2 StampedLock - 2.2.1 使用 - 2.2.2 原理

1. synchronized的不足

虽然synchronized效率已经上来了,但是对比lock还是有一些缺点,主要还是灵活性方面。

    无法实现乐观读锁 sleep的时候不释放锁会导致所有线程阻塞

常见锁的分类

    可重入锁和非可重入锁,锁两次,是否已经作处理 公平锁非公平锁 读写锁和排他锁,排他锁是独占的,只有一个线程,读写锁可以是乐观锁的实现

2. JDK中的锁实现

2.1 AQS

参考AQS,此外,JDK中还有AQLS,它将资源标记换为long,AOS是它们的父类。

ReentrantLock

重要的独占锁参考AQS。这个锁是理解AQS的基础,也是理解其他锁的基础——其实是因为锁的实现比较类似。

2.1.1 使用

ReentrantReadWriteLock,这是JDK实现的一个读写锁,它包含读锁和写锁,其使用也简单。

但是它的缺点是,写锁是独占锁,一旦写,则读写线程都得阻塞。

1 private final ReadWriteLock rwlock = new ReentrantReadWriteLock(); 2 private final Lock rlock = rwlock.readLock(); 3 private final Lock wlock = rwlock.writeLock();

2.1.2 原理

它的原理和ReentrantLock的区别是,它维护两对锁。

读锁和写锁有什么区别呢?一看源码就能理解了。

1 // 读锁 2 public void lock() { 3 sync.acquireShared(1); 4 } 5 // 写锁 6 public void lock() { 7 sync.acquire(1); 8 }

2.2 StampedLock

这与AQS平行的另一个锁实现,解决了ReentrantReadWriteLock的缺陷,即在写的时候也可以支持并发的读,它属于乐观锁实现。

在读的时候如果发生了写,应该通过重试的方式来获取新的值,而不应该阻塞写操作。这种模式也就是典型的无锁编程思想,和CAS自旋的思想一样。

2.2.1 使用

以下逻辑是,乐观读取,但万一发现发生了写(状态改变),那就需要使用悲观锁。

1 // 乐观读锁的使用 2 double distanceFromOrigin() { 3 long stamp = sl.tryOptimisticRead(); // 获取乐观读锁 4 double currentX = x, currentY = y; 5 if (!sl.validate(stamp)) { // //检查乐观读锁后是否有其他写锁发生,有则返回false 6 stamp = sl.readLock(); // 获取一个悲观读锁 7 try { 8 currentX = x; 9 currentY = y; 10 } finally { 11 sl.unlockRead(stamp); // 释放悲观读锁 12 } 13 } 14 return Math.sqrt(currentX * currentX + currentY * currentY); 15 }

2.2.2 原理

从使用中可以看到,类似于乐观锁的实现,它是通过控制一个状态值(version)来指示出以乐观锁的方式去读取后,在这期间是否发生了改变。

从源码来看,状态实现蛮繁杂的。简单概括:

    对于悲观锁的加锁和释放,分别对应着状态的加减 而悲观锁写锁,不管加锁或者解锁,状态只会增加,这是为了防止ABA问题 状态值的修改都是通过CAS实现的
经验分享 程序员 微信小程序 职场和发展