5. Java中的锁

2020-02-25  本文已影响0人  ygxing

本文将介绍Java并发包中与锁相关的API和组件, 以及这些API和组件的使用方式和实现细节

1. Lock接口

锁是用来控制多个线程访问共享资源的方式, 像独占锁, 读写锁

在Lock接口出现之前, java程序考synchronized关键字实现锁功能, 在JDK1.5之后, 并发包中新增了Lock接口, 用来实现锁功能

Lock使用方式, 有两个注意点:

  1. 在finally中释放锁, 目的是保证在获取到锁之后, 最终能够被释放
  2. 在try语句之前获取锁, 因为获取锁的过程中可能会产生异常, 而获取不到锁, 获取不到锁, 再去释放锁, 就会抛出IllegalMonitorStateException异常
public class LockExample {
    public static void main(String[] args) {
        // 实例化锁
        Lock lock = new ReentrantLock();
        // 获取锁
        lock.lock();
        try {
            // doSomething...
        } finally {
            // 释放锁
            lock.unlock();
        }
    }
}
1.1 Lock接口和synchronized关键字对比, 优势
  1. 尝试非阻塞的获取锁, 当线程尝试获取锁, 如果这一时刻锁没有被其他线程获取到, 则成功获取并持有锁
  2. 能够被中断的获取锁, 获取到锁的线程能够响应中断, 当获取到锁的线程被中断时, 中断异常将会被抛出, 同时锁会被释放
  3. 超时获取锁, 在指定的截止时间内获取锁, 如果超时之后仍旧无法获取锁, 那么立即返回
1.2 Lock接口API
  1. void lock(), 获取锁, 调用该方法时, 当前线程将会获取锁, 当锁获取之后, 从该方法返回
  2. void lockInterruptibly(), 可中断的获取锁, 和lock()方法的不同之处在于该方法会相应中断, 即在锁的获取过程中, 可以终端当前线程
  3. boolean tryLock(), 尝试非阻塞的获取锁, 调用该方法后立即返回
    • 如果能获取到锁, 返回true
    • 如果获取不到锁, 返回false
  4. void unlock(), 释放锁
  5. Condition newCondition(), 获取等待通知组件, 该组件和当前的锁绑定, 当前线程获取锁之后, 才能调用Condition组件的方法, Condition接口与Lock接口配合, 实现了线程等待/通知机制, 实现了Object类的wait和notify的功能
    • Condition#await()作用: 当前线程将释放锁, 当其他线程调用Condition#signal或signalAll方法, 当前线程获取到锁之后, 才会从await()方法返回, 作用类似于Object#wait(), 如果当前线程等待过程中被中断, 那么抛出被中断异常
    • Condition#signal()作用, 唤醒一个等待Condition的线程, 将该线程从等待队列移动到同步队列中, 被唤醒队列参与到Lock的竞争中, 如果获取到Lock, 会从等待方法中返回

2. 队列同步器AQS

队列同步器AbstractQueuedSynchronizer,用来构建锁或者其他同步其他组件的基础框架,它使用int类型的成员变量state表示同步状态,通过内置的FIFO队列来完成线程排队工作

同步器是实现锁的关键,利用同步器实现锁的语义

  1. 锁面向使用者, 定义了使用者与锁交互的接口, 隐藏了锁的实现细节
  2. 同步器面试锁的实现者, 简化了锁的实现方式, 屏蔽了同步状态管理, 线程排队, 等待与唤醒等底层操作
  3. 锁和同步器很好的隔离了使用者和实现者所需关注的领域
2.1 同步器DEMO

独占锁MutexLock是一个自定义同步组件, 再同一时刻只有一个线程站有锁

因为是独占锁, 所以Sync不需要实现acquireShared和releaseShared等方法

//独占锁
public class MutexLock implements Lock {
    //同步器
    private Sync sync = new Sync();

    //独占上锁, 不响应中断
    //先尝试获取锁
    //失败的话新建一个Node节点, 将当前线程加入到同步队列尾部
    //再尝试CAS获取锁
    //获取不到锁的话, 进入等待状态, 等待被唤醒
    //唤醒之后继续获取锁
    @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);
    }

    //Condition类, 多线程间协调通信的工具类
    //Condition#await()方法相当于Object#wait()方法
    //Confition#signal()方法相当于Object#notify()方法
    @Override
    public Condition newCondition() {
        return sync.newCondition();
    }

    //同步器
    private class Sync extends AbstractQueuedSynchronizer {
        //尝试获取锁
        @Override
        protected boolean tryAcquire(int arg) {
            if (compareAndSetState(0, 1)) {
                //CAS将state字段设置为1
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        //尝试释放锁
        @Override
        protected boolean tryRelease(int arg) {
            if (getState() == 0) {
                throw new IllegalMonitorStateException();
            }
            //将state设为0
            setState(0);
            return true;
        }

        @Override
        protected boolean isHeldExclusively() {
            //独占锁
            return true;
        }

        Condition newCondition() {
            return new ConditionObject();
        }
    }
}
2.2 同步器实现原理

同步器主要由同步队列, 独占状态的获取与释放, 共享状态的获取与释放等模板方法构成

上一篇下一篇

猜你喜欢

热点阅读