AQS面试详解

AQS原理

AQS(AbstractQueuedSynchronizer)抽象同步队列。是除了java自带的synchronized关键字之外的锁机制。它是实现同步器的基础组件,并发包中锁的底层就是使用AQS实现的。

AQS类图如下:

由该图可以看到,AQS是一个FIFO的双向队列,其内部通过节点head和tail记录队首和队尾元素,队列元素类型为Node。其中Node中的thread变量用来存放进入AQS队列里面的线程;Node节点内部的SHARED用来标记该线程是获取共享资源时被阻塞挂起后放入AQS队列的,EXCLUSIVE用来标记该线程是获取独占资源时被挂起后放入AQS队列的;waitStatus记录当前线程等待状态,可以为CANCELLED(线程被取消了)、SIGNAL(线程需要被唤醒)、CONDITION(线程在条件队列里面等待)、PROPAGATE(释放共享资源时需要通知其他节点);prev记录当前节点的前驱节点,next记录当前节点的后继节点。

AQS的核心思想:

如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并将共享资源设置为锁定状态,如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。

CLH队列是一个虚拟的双向队列,虚拟的双向队列即不存在队列实例,仅存在节点之间的关联关系。

AQS是将每一条请求共享资源的线程封装成一个CLH锁队列的一个节点(Node),来实现锁的分配。

简单来说,AQS就是基于CLH队列,用volatile修饰共享变量state,线程通过CAS去改变装态符,成功则获取锁成功,失败则进入等待队列,等待被唤醒。

AQS是自旋锁,再等待被环形的时候,经常会使用自旋的方式,不停地尝试获取锁,直到被其他线程获取成功。

AQS-条件变量的支持

notify和wait,是配合synchronized内置锁实现线程间同步的基础设施一样,条件变量的signal和await方法也是用来配合锁(使用AQS实现的锁)实现线程间同步的基础设施。

它们的不同在于,synchronized同时只能与一个共享变量的notify或wait方法实现同步,而AQS的一个锁可以对应多个条件变量。

在调用共享变量的notify和wait方法前必须先获取该共享变量的内置锁,同理,在调用条件变量的signal和await方法前也必须先获取条件变量对应的锁。

总结如下:一个锁对应一个AQS阻塞队列,对应多个条件变量,每个条件变量有自己的一个条件队列。

基于AQS实现自定义同步器:

// 基于AQS实现不可重入的独占锁
class NonReentrantLock implements Lock, java.io.Serializable {
    // 内部帮助类
    private static class Sync extends AbstractQueuedSynchronizer {
        // 是否已经被持有
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }

        // 如果sate为0 则尝试获取锁
        public boolean tryAcquire(int acquires) {
            assert acquires == 1;
            if (compareAndSetState(0,1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }
        
        // 尝试释放锁,设置state为0
        protected boolean tryRelease(int releases) {
            assert releases == 1;
            if (getState() == 0) {
                throw new IllegalMonitorStateException();
            }
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        // 提供条件变量接口
        Condition newCondition() {
            return new ConditionObject();
        }
    }

    // 创建一个Sync来做具体的工作
    private final Sync sync = new Sync();

    @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();
    }

    public boolean isLocked() {
        return sync.isHeldExclusively();
    }
}
  • NonReentrantLock定义了一个内部类Sync用来实现具体的锁的操作,Sync则继承了AQS。由于我们实现的是独占模式的锁,所以Sync重写了tryAcquire、tryRelease和isHeldExclusively3个方法。另外,Sync提供了newCondition这个方法用来支持条件变量

使用自定义锁实现生产-消费模型:

public class ProducerTest {
    final static NonReentrantLock lock = new NonReentrantLock();
    // 创建两个变量,进行生产者和消费者之间的同步
    final static Condition notFull = lock.newCondition();
    final static Condition notEmpty = lock.newCondition();

    final static Queue<String> queue = new LinkedBlockingQueue<String>();
    final static int queueSize = 10;

    public static void main(String[] args) {
        Thread producer = new Thread(new Runnable() {
            @Override
            public void run() {
                // 获取独占锁
                lock.lock();
                try {
                    // 如果队列满了,则等待
                    // 使用while而不是if是为了防止虚假唤醒
                    while (queue.size() == queueSize) {
                        notEmpty.await();
                    }

                    // 添加元素到队列
                    queue.add("ele");
                    System.out.println("生产一个元素");
                    // 唤醒消费线程
                    // 唤醒所有因为消费元素而被阻塞的消费线程
                    notFull.signalAll();
                }catch (Exception e) {
                    e.printStackTrace();
                }finally {
                    // 释放锁
                    lock.unlock();
                }
            }
        });

        Thread consumer = new Thread(new Runnable() {
            @Override
            public void run() {
                // 获取独占锁
                lock.lock();
                try {
                    // 队列空,则等待
                    while(0 == queue.size()) {
                        notFull.await();
                    }
                    // 消费一个元素
                    String ele = queue.poll();
                    System.out.println("消费一个元素");
                    // 唤醒生产线程
                    notEmpty.signalAll();
                }catch (Exception e) {
                    e.printStackTrace();
                }finally {
                    // 释放锁
                    lock.unlock();
                }
            }
        });

        // 启动线程
        producer.start();
        consumer.start();
    }

}
  • 如上代码首先创建了NonReentrantLock的一个对象lock,然后调用了lock.newCodition创建了两个条件变量,用来进行生产者和消费者线程之间的同步。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值