在进行更近一步的了解Java锁的知识之前,我们需要先了解与锁有关的两个概念 CAS 与 AQS。关注我的公众号「Java面典」了解更多 Java 相关知识点。
CAS(Compare And Swap/Set)
概念
CAS函数,是比较并交换函数,它是原子操作函数。
原理
CAS 是基于乐观锁的原理进行操作的。它总是认为自己可以成功完成操作。当多个线程同时使用 CAS 操作一个变量时,只有一个会胜出,并成功更新,其余均会失败。失败的线程不会被挂起,仅是被告知失败,并且允许再次尝试或放弃操作。
实现
- 构造:CAS 包含 3 个参数CAS(V,E,N)。V 表示要更新的变量(内存值),E 表示预期值(旧的),N 表示新值;
- 比较并交换:当且仅当 V 值等于 E 值时,才会将 V 的值设为 N;如果 V 值和 E 值不同,则说明已经有其他线程做了更新,当前线程什么都不做;
- 返回:CAS 返回当前 V 的真实值。
ABA 问题
CAS 会导致“ABA 问题”。CAS 算法实现一个重要前提需要取出内存中某时刻的数据,而在下时刻比较并替换,那么在这个时间差内会导致数据的变化。
ABA 例子:比如说一个线程 one 从内存位置 V 中取出 A,这时候另一个线程 two 也从内存中取出 A,并且two 进行了一些操作变成了 B,然后 two 又将 V 位置的数据变成 A,这时候线程 one 进行 CAS 操作发现内存中仍然是 A,然后 one 操作成功。尽管线程 one 的 CAS 操作成功,但是不代表这个过程就是没有问题的。
ABA 解决方案:部分乐观锁的实现是通过版本号(version)的方式来解决 ABA 问题,乐观锁每次在执行数据的修改操作时,都会带上一个版本号,一旦版本号和数据的版本号一致就可以执行修改操作并对版本号执行+1 操作,否则就执行失败。因为每次操作的版本号都会随之增加,所以不会出现 ABA 问题,因为版本号只会增加不会减少。
AQS
概念
AQS即AbstractQueuedSynchronizer (抽象的队列式的同步器),AQS 定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,如常用的ReentrantLock/Semaphore/CountDownLatch。
AQS 维护了一个 volatile int state(代表共享资源)和一个 FIFO 线程等待队列(多线程争用资源被阻塞时会进入此队列)。这里 volatile 是核心关键词,具体 volatile 的语义,在此不述。state 的访问方式有三种: getState() setState() compareAndSetState()
原理(state 资源状态计数)
计数器:同步器的实现是 ABS 核心,以 ReentrantLock 为例,state 初始化为 0,表示未锁定状态。A 线程 lock()时,会调用 tryAcquire() 独占该锁并将 state+1。此后,其他线程再 tryAcquire() 时就会失败,直到 A 线程 unlock() 到 state=0(即释放锁)为止,其它线程才有机会获取该锁。
可重入锁:在 A 线程释放锁之前,A 线程自己是可以重复获取此锁的(state 会累加),这就是可重入锁的实现。 **但要注意,获取多少次就要释放多么次,这样才能保证 state 是能回到零态的
**。
资源共享方式
AQS 定义了两种资源共享方式:
- Exclusive 独占资源:Exclusive(独占,只有一个线程能执行,如 ReentrantLock);
- Share 共享资源:Share(共享,多个线程可同时执行,如Semaphore/CountDownLatch)。
一般来说,自定义同步器要么是独占方法,要么是共享方式,他们也只需实现 tryAcquire-tryRelease、tryAcquireShared-tryReleaseShared 中的一种即可。但 AQS 也支持自定义同步器同时实现独占和共享两种方式,如 ReentrantReadWriteLock.
实现方式
AQS 只是一个框架,具体资源的获取/释放方式交由自定义同步器去实现。自定义同步器实现时主要实现以下几种方法: 1.isHeldExclusively():该线程是否正在独占资源。只有用到 condition 才需要去实现它。 2.tryAcquire(int):独占方式,尝试获取资源。成功则返回 true,失败则返回 false。 3.tryRelease(int):独占方式,尝试释放资源。成功则返回 true,失败则返回 false。 4.tryAcquireShared(int):共享方式,尝试获取资源。负数表示失败;0 表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。 5.tryReleaseShared(int):共享方式,尝试释放资源。如果释放后允许唤醒后续等待结点返回true,否则返回 false。
多线程与并发系列推荐
Java多线程并发03——什么是线程上下文,线程是如何调度的