Java并发编程的艺术(六)——Java中的锁(1)

1. Lock接口

1.1 Lock接口和synchronized关键字的对比

Lock接口在出现以前,Java使用synchronized关键字实现锁的功能。而在Java 1.5之后,Java新增了Lock接口及其实现类来实现类似synchronized关键字的功能。与synchronized隐式获取/释放锁不同的是,Lock接口获取/释放锁是显式的(需要调用Lock.lock()、Lock.unlock()方法)。此外Lock接口还拥有中断获取锁的线程、超时获取锁、尝试非阻塞获取锁等多种synchronized关键字所不具备的功能。

1.2 Lock接口的使用

    Lock lock = new ReentrantLock();
    lock.lock();
    try {
        ...
    } finally {
        lock.unlock();
    }
复制代码

1.3 使用Lock接口的注意事项

●不要将锁的获取放在try块中,因为若在锁的获取过程中(此时还未真正获取到锁)发生了异常,则在finally块中锁将会被无故释放(释放未获取到的锁也会发生异常)。

●要在finally块中释放锁,保证在获取到锁后,锁能被释放。

1.4 Lock接口的API

方法名称 描述
void lock() 当前线程尝试获取锁
void lockInterruptibly() throws InterruptedException 可中断地获取锁,即在锁的获取中可以中断当前线程
boolean tryLock() 尝试非阻塞地获取锁,调用该方法后立即返回,获取锁成功返回true,否则返回false
boolean tryLock(long time,TimeUnit unit) throws InterruptedException 超时获取锁
void unlock() 释放锁
Condition newCondition() 获取等待/通知组件,该组件和当前的锁绑定,当前线程只有获得了锁才能调用该组件的await()方法,调用await()方法后当前线程也将释放锁

2. 队列同步器AbstractQueuedSynchronizer(AQS)

2.1 AQS的理解

AQS是构建锁或其他同步组件的基础框架,它使用了一个int成员变量来表示同步状态,通过内置的FIFO队列来完成资源获取线程(通常为获取锁的线程)的排队工作。

2.2 AQS的使用

简单来说,AQS的主要使用方法是被子类继承,并且子类被推荐定义为同步组件的静态内部类。同步组件中聚合了AQS,利用AQS实现同步语义。(上述同步组件可以简单理解为自定义的锁)

2.3 自定义同步组件(锁)和AQS的关系

锁是面向使用者的,锁定义了可供使用者调用的接口,屏蔽了锁的实现细节。AQS是面向锁的实现者的,它帮助简化了锁的实现,屏蔽了同步状态的管理、线程的排队、等待和唤醒等底层操作。锁和AQS很好地隔离了使用者和实现者所需关注的领域。

2.4 同步器可重写的方法(可重写的意思是根据需求自己重写某些方法)

方法名称 描述
protected boolean tryAcquire(int arg) 独占式获取同步状态,实现该方法需要查询同步状态并判断其是否符合预期,若符合则用CAS设置成新的状态。
protected tryRelease(int arg) 独占式释放同步状态。
protected int tryAcquireShared(int arg) 共享式获取同步状态,若符合值大于等于0,表示获取同步状态成功,反之失败。
protected boolean tryReleaseShared(int arg) 共享式释放同步状态。
protected boolean isHeldExclusively() 当前同步状态是否在独占模式下已被获取。

2.5 同步器提供的模板方法

方法名称 描述
void acquire(int arg) 独占式获取同步状态。若获取成功将从该方法返回,若获取失败线程将进入同步队列等待。此外该方法调用了重写的tryAcquire(int arg)方法。
void acquireInterruptibly(int arg) 该方法是void acquire(int arg)方法的中断响应版本。进入同步队列的线程可被中断并抛出InterruptedException异常。
boolean tryAcquireNanos(int arg,long nanos) 该方法是void acquireInterruptibly(int arg)方法的超时等待版本。
void acquireShared(int arg) 共享式获取同步状态,和独占式获取的主要区别是同一时刻可以后多个线程获得同步状态。
void acquireSharedInterruptibly(int arg) 该方法是void acquireShared(int arg)方法的中断响应版本。
boolean tryAcquireSharedNanos(int arg,long nanos) 该方法是void acquireSharedInterruptibly(int arg)方法的超时等待版本。
boolean release(int arg) 独占式释放同步状态。该方法将会调用重写的tryRelease(int arg)方法。
boolean releaseShared(int arg) 共享式释放同步状态。
Collection< Thread >getQueueThreads() 获取等待在同步队列上的线程集合。

2.5 同步器的实现

同步器的设计是基于模板设计模式的,即自定义同步组件里的静态内部类继承了AQS后,重写了指定的方法。而同步器里的模板方法将被自定义同步组件调用,模板方法里则调用了重写的方法。

class Mutex implements Lock{
    //静态内部类,自定义同步器
    private static class Sync extends AbstractQueueSynchronizer{
        //是否处于占用状态
        protected boolean isHeldExclusively(){
            return getState() == 1;
        }
        //当状态为0的时候获取锁
        public boolean tryAcquire(int acquires){
            if(compareAndSetState(0,1)){
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }
        //释放锁,将状态设置为0
        protected boolean tryRelease(int release){
            if(getState() == 0)throw new IllegalMonitorStateException();
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }
        //返回一个Condition,每个condition都包含了一个condition队列
        Condition newCondition(){
            return new Condition();
        }
    }
    //仅需要将操作代理到Sync上即可
    private final Sync sync = new Sync();
    public void lock(){sync.acquire(1)};
    public boolean tryLock(){
        return sync.tryAcquire(1);
    }
    public void unlock(){
        sync.release(1);
    }
    public Condition newCondition(){
        return sync.newCondition();
    }
    public boolean isLocked(){
        return sync.isHeldExclusively();
    }
    public boolean hasQueuedTreads(){
        return sync.hasQueuedTreads();
    }
    public void lockInterruptibly() throws InterruptedException{
        sync.acquireInterruptibly(1);
    }
    public boolean tryLock(long timeout,TimeUnit unit) throws InterruptedException{
        return sync.tryAcquireNanos(1,unit.toNanos(timeout));
    }
}
复制代码

猜你喜欢

转载自juejin.im/post/5cf9bb6c51882512a675f9b0