关于java:从零到一实现互斥锁理清AQS和Lock的关系

39次阅读

共计 3076 个字符,预计需要花费 8 分钟才能阅读完成。

1. AQS 原理

1.1 AQS 简介

AQS 全名 AbstractQueuedSynchronizer,是一个队列同步器,JUC 的锁和同步组件都是基于 AQS 构建的。AQS 的构造次要分两局部:

  • state:用了 violate 关键字润饰的 int 类型成员变量,示意同步状态,每次更新都能被所有线程可见;
  • head、tail:内置的 Node 类型(Node 蕴含以后线程、期待状态等信息),组成了一个FIFO 的同步队列

AQS 的次要应用形式是继承,它采纳了模板办法的设计模式。什么是模板办法设计模式?简略来说,就是 一个类将一些不变行为封装成 final 办法(即模板办法),将残余的可变办法(abstract)留给子类实现去拓展

1.2 AQS 接口

AQS 次要提供了如下三个办法来拜访或批改同步状态 state:

  • getState():获取以后同步状态
  • setState(int newState):设置以后同步状态
  • compareAndSetState(int expect, int update):应用 CAS 设置以后状态,该办法可能保障状态设置的原子性。

1.2.1 AQS 模板办法

上面列举几个最为外围的模板办法:

办法 形容
void acquire(int arg) 独占式调用 tryAquire()尝试获取同步状态,获取失败则将该线程塞进同步队列队尾
void acquireShared(int arg) 共享式调用 tryAquireShared()获取同步状态,同一时刻能够有多个线程获取到同步状态
boolean release(int arg) 独占式调用 tryRelease()开释同步状态
boolean releaseShared(int arg) 共享式调用 tryReleaseShared()开释同步状态

1.2.2 AQS 重写接口

而 AQS 可重写的办法如下:

办法 形容
boolean tryAquire(int arg) 独占式获取同步状态
boolean tryRelease(int arg) 独占式开释同步状态
int tryAquireShared(int arg) 共享式获取同步状态,与其余接口不同的是该接口返回值是 int,这是为了实现共享的语义,返回大于等于 0 时示意胜利,反之失败
boolean tryReleaseShared(int arg) 共享式开释同步状态

能够看到,AQS 利用模板办法次要做了两件事:

  1. 尝试获取或设置同步状态
  2. 保护同步队列

而留给开发者的可重写办法更多聚焦于如何 设置、获取同步状态,这种设计思维与双亲委派机制有点类似:loadClass 中实现了双亲委派机制,而用户能够通过重写 findClass 去实现本人加载类的具体逻辑。

2. Lock 和 AQS 的关系

AQS 是实现 Lock 的根底,两者之间的次要区别在于:

  • Lock 面向锁的使用者,它聚焦的问题是 使用者如何更好地应用锁解决并发问题,而使用者不须要晓得锁的实现细节就能够实现互斥同步;
  • AQS 面向锁的开发者,它关注的两个次要问题是 同步状态治理 以及 保护线程的同步期待队列

3. 自定义实现独占锁

该节通过自定义实现独占锁 Demo,去更加清晰揭示 AQS 和 Lock 接口的区别。

首先,咱们定义了一个独占锁—Mutex实现 Lock 接口,并实现该接口的所有办法。而 Lock 接口的办法实现中所做的是都是调用 AQS 的模板办法。

其次,咱们创立了一个外部类—Sync,实现了一个自定义同步器,并重写了其中的所有非模板办法,次要思维是 通过 getState()、setState(int newState)和 compareAndSetState(int expect, int update)三个办法去设置和获取同步状态,具体实现可看代码正文。

至此咱们总结一下其中的一个外围调用链lock --> acquire() --> tryAcquire()

  • lock:面向用户提供锁性能
  • acquire:尝试设置同步状态并保护同步队列
  • tryAquire:设置同步状态的真正逻辑
import java.util.concurrent.TimeUnit;  
import java.util.concurrent.locks.AbstractQueuedSynchronizer;  
import java.util.concurrent.locks.Condition;  
import java.util.concurrent.locks.Lock;  
​  
/**  
 * @author Lam  
 * @date 2020/7/27  
 */  
public class Mutex implements Lock {private final Sync sync = new Sync();  
    ​  
    /**  
     * 同步器实现,重写非模板办法  
     * 次要思维是应用 CAS 设置 state 的状态  
     */  
    private static class Sync extends AbstractQueuedSynchronizer {  

        @Override  
        protected boolean tryAcquire(int arg) {  
            // CAS 设置 state  
            if (compareAndSetState(0, 1)) {  
                // 设置该线程为胜利获取同步状态的线程  
                setExclusiveOwnerThread(Thread.currentThread());  
                return true;  
            }  

            // 设置状态失败  
            return false;  
        }  
        ​  
        @Override  
        protected boolean tryRelease(int arg) {  
            // 若此时同步状态为 0 阐明没有线程设置过同步状态  
            if (getState()==0) {throw new IllegalMonitorStateException();  
            }  

            // 清空胜利获取同步状态的线程信息  
            setExclusiveOwnerThread(null);  

            // 革除状态  
            setState(0);  
            return true;  
        }  
        ​  
        @Override  
        protected boolean isHeldExclusively() {return getState() == 1;  
        }  
        ​  
        /**  
         * 该办法不是 AQS 的重写办法,只是为了代码格调对立将逻辑都放到 Sync 中  
         */  
        Condition newCondition() {  
            // 返回一个期待告诉组件  
            return new ConditionObject();}  
    }  
​  
    /**  
    * Lock 接口的实现  
    */  
    ​  
    @Override  
    public void lock() {sync.acquire(1);  
    }  
    ​  
    @Override  
    public void lockInterruptibly() throws InterruptedException {sync.acquireInterruptibly(1);  
    }  
    ​  
    @Override  
    public boolean tryLock() {return sync.tryAcquire(1);  
    }  
    ​  
    @Override  
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {return sync.tryAcquireNanos(1, unit.toNanos(time));  
    }  
    ​  
    @Override  
    public void unlock() {sync.release(1);  
    }  
    ​  
    @Override  
    public Condition newCondition() {return sync.newCondition();  
    }  
}

4. 总结

本文大部分内容取自 Java 并发编程的艺术,在一些小中央做了删改并加了本人的了解,如果想更齐全深刻理解 AQS 的模板办法和非模板办法有哪些能够查看原书第五章的 1、2 节。

正文完
 0