显式锁和AQS

旧街凉风 提交于 2020-04-29 09:29:17
Lock 的标准用法
lock.lock();
try {
     count++;
} finally{
lock.unlock();
}
Lock 的常用 API
 
ReentrantLock 锁的可重入
“同一个线程对于已经获得到的锁,可以多次继续申请到该 锁的使用权”。
synchronized 关键字隐式的支持重进入,比如一个 synchronized 修饰的递归方法,在方法执行时,执行线程在获取了锁之后仍能连续多次地获得 该锁。
ReentrantLock 在调用 lock() 方法时,已经获取到锁的线程,能够再次调用 lock() 方法获取锁而不被阻塞。
 
读写锁 ReentrantReadWriteLock
ReentrantReadWriteLock 其实实现的是 ReadWriteLock 接口。
之前提到锁(如 Mutex ReentrantLock )基本都是排他锁,这些锁在同一 时刻只允许一个线程进行访问,而读写锁在同一时刻可以允许多个读线程访问, 但是在写线程访问时,所有的读线程和其他写线程均被阻塞。读写锁维护了一对 锁,一个读锁和一个写锁,通过分离读锁和写锁,使得并发性相比一般的排他锁 有了很大提升。
一般情况下,读写锁的性能都会比排它锁好,因为大多数场景读是多于写的。 在读多于写的情况下,读写锁能够提供比排它锁更好的并发性和吞吐量
Condition 接口
任意一个 Java 对象,都拥有一组监视器方法(定义在 java.lang.Object 上), 主要包括 wait() wait(long timeout) notify() 以及 notifyAll() 方法,这些方法与 synchronized 同步关键字配合,可以实现等待 / 通知模式。 Condition 接口也提供 了类似 Object 的监视器方法,与 Lock 配合可以实现等待 / 通知模式
condition使用范式
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
public void conditionWait() throws InterruptedException{
    lock.lock();
    try {
        condition.await();
    } finally {
        lock.unlock();
    }
}
public void conditionSignal() throws InterruptedException{
    lock.lock();
    try {
        condition.signal();
    } finally {
        lock.unlock();
    }

}
了解 LockSupport
LockSupport 定义了一组的公共静态方法,这些方法提供了最基本的线程阻 塞和唤醒功能,而 LockSupport 也成为构建同步组件的基础工具。 LockSupport 定义了一组以 park 开头的方法用来阻塞当前线程,以及 unpark(Thread thread) 方法来唤醒一个被阻塞的线程。
LockSupport 增加了
park(Object blocker)
parkNanos(Object blocker,long nanos)
parkUntil(Object blocker,long deadline)
方法,用于实现阻塞当前线程的功能,其中参数 blocker 是用来标识当前线程在等待的对象(以下称为阻塞对象),该对象主要用于问题 排查和系统监控。

 

CLH 队列锁

CLH 队列锁即 Craig, Landin, and Hagersten (CLH) locks ,这三个人的名字首字母组成
CLH 队列锁也是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程 仅仅在本地变量上自旋,它不断轮询前驱的状态,假设发现前驱释放了锁就结束 自旋。
当一个线程需要获取锁时:
1. 创建一个的 QNode ,将其中的 locked 设置为 true 表示需要获取锁, myPred 表示对其前驱结点的引用

2. 线程 A tail 域调用 getAndSet 方法,使自己成为队列的尾部,同时获取 一个指向其前驱结点的引用 myPred
线程 B 需要获得锁,同样的流程再来一遍

3. 线程就在前驱结点的 locked 字段上旋转,直到前驱结点释放锁 ( 前驱节点 的锁值 locked == false)
4. 当一个线程需要释放锁时,将当前结点的 locked 域设置为 false ,同时回收 前驱结点
 
如上图所示,前驱结点释放锁,线程 A myPred 所指向的前驱结点的 locked 字段变为 false ,线程 A 就可以获取到锁。
CLH 队列锁的优点是空间复杂度低(如果有 n 个线程, L 个锁,每个线程每 次只获取一个锁,那么需要的存储空间是 O L+n ), n 个线程有 n myNode L 个锁有 L tail )。 CLH 队列锁常用在 SMP 体系结构下。 Java 中的 AQS CLH 队列锁的一种变体实现。
 
AbstractQueuedSynchronizer
 
AQS 使用方式和其中的设计模式
AQS 的主要使用方式是继承,子类通过继承 AQS 并实现它的抽象方法来管 理同步状态,在 AQS 里由一个 int 型的 state 来代表这个状态,在抽象方法的实 现过程中免不了要对同步状态进行更改,这时就需要使用同步器提供的 3 个方法 getState() setState(int newState) compareAndSetState(int expect,int update) 来进行操作,因为它们能够保证状态的改变是安全的。
 
模板方法模式
同步器的设计基于模板方法模式。模板方法模式的意图是,定义一个操作中 的算法的骨架,而将一些步骤的实现延迟到子类中。模板方法使得子类可以不改 变一个算法的结构即可重定义该算法的某些特定步骤。我们最常见的就是 S pring 框架里的各种 Template
 
AQS 中的方法
模板方法
实现自定义同步组件时,将会调用同步器提供的模板方法,
 
可重写的方法
 
访问或修改同步状态的方法
重写同步器指定的方法时,需要使用同步器提供的如下 3 个方法来访问或修 改同步状态。
getState() :获取当前同步状态。
setState(int newState) :设置当前同步状态。
compareAndSetState(int expect,int update) :使用 CAS 设置当前状态,该方 法能够保证状态设置的原子性
 
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!