AQS,每天一个面试题

AQS是多线程同步器,它是JUC包中多个组建的底层实现,比如说像Lock,CountDownLatch,Semaphore都用到了AQS,从本质上来说AQS提供了两种锁的机制,分别是排他锁和共享锁,所谓排他锁就是存在多个线程去竞争同一共享资源的时候,同一个时刻只允许一个线程去访问这样一个共享资源,也就是说多个线程中,只能有一个线程去获得这样的一个锁的资源,比如Lock中的ReentrantLock重入锁,他的实现就是用到了AQS中的一个排他锁的功能。共享锁也称为读锁,就是同一时刻允许多个线程同时获得这样一个锁的资源,比如CountDownLatch以及Semaphore,都用到了AQS中的共享锁的功能。那么AQS作为互斥锁来说呢,他的整个设计体系中,需要解决三个核心的问题。第一个互斥变量的设计,以及如何保证多线程更新互斥变量的时候线程的安全性,第二个未竞争到锁资源的线程的等待,以及竞争到锁的线程,释放锁之后的唤醒,第三个锁竞争的公平性和非公平性。AQS采用了一个int类型的互斥变量state用来记录锁竞争的一个状态,0表示当前没有任何线程竞争锁资源,而大于等于1表示已经有线程正在持有锁资源。一个线程来获取锁资源的时候,首先判断state是否等于0,也就是无锁状态,如果是则把这个state更新成1,而这个过程中,如果多个线程同时去做这样一个操作,就会导致线程安全性的问题,因此AQS采用了CAS机制,去保证state互斥变量更新的一个原子性。未获取锁的线程通过unsafe类中的park方法去进行阻塞,把阻塞的线程按照先进先出的原则去加入到一个双向链表的一个结构中,当获得锁资源的线程释放锁之后,会从这样一个双向链表的头部去唤醒下一个等待的线程,再去竞争锁。最后关于锁竞争的公平性和非公平性的问题AQS的处理方法是,在竞争锁资源的时候公平锁需要去判断双向链表中是否有阻塞的线程,如果有则需要去排队等待,而非公平锁的处理方式是不管双向链表中是否存在阻塞的线程,那么他都会直接去尝试更改互斥变量state去竞争锁,假设在一个临界点,获得锁的线程释放锁,此时state等于0,而当前的这个线程去抢占锁的时候正好可以把state修改成1,那么这个时候就表示他可以拿到锁,这个过程是非公平的。

synchronized和Lock有什么区别

标准回答 synchronized和Lock都是锁,都是线程同步的手段,它们的区别主要体现在如下三个方面: 1. 使用方式的区别 synchronized关键字可以作用在静态方法、实例方法和代码块上,它是一种隐式锁,即我们无需显式地获取和释放锁,所以使用起来十分的方便。在这种同步方式下,我们需要依赖Monitor(同步监视器)来实现线程通信。若关键字作用在静态方法上,则Monitor就是当前类的Class对象;若关键字作用在实例方法上,则Monitor就是当前实例(this);若关键字作用在代码块上,则需要在关键字后面的小括号里显式指定一个对象作为Monitor。 Lock接口是显式锁,即我们需要调用其内部定义的方法显式地加锁和解锁,相对于synchronized来说这显得有些繁琐,但是却提供了更大的灵活性。在这种同步方式下,我们需要依赖Condition对象来实现线程通信,该对象是由Lock对象创建出来的,依赖于Lock。每个Condition代表一个等待队列,而一个Lock可以创建多个Condition对象。相对而言,每个Monitor也代表一个等待队列,但synchronized只能有一个Monitor。所以,在实现线程通信方面,Lock接口具备更大的灵活性。 2. 功能特性的区别 synchronized是早期的API,Lock则是在JDK 1.5时引入的。在设计上,Lock弥补了synchronized的不足,它新增了一些特性,均是synchronized不具备的,这些特性包括: - 可中断地获取锁:线程在获取锁的过程中可以被中断。 - 非阻塞地获取锁:该方法在调用后立刻返回,若能取到锁则返回true,否则返回false。 - 可超时地获取锁:若线程在到达超时时间后仍未获得锁,并且线程也没有被中断,则返回false。 3. 实现机制的区别 synchronized的底层是采用Java对象头来存储锁信息的,对象头包含三部分,分别是Mark Word、Class Metadata Address、Array length。其中,Mark Word用来存储对象的hashCode及锁信息,Class Metadata Address用来存储对象类型的指针,而Array length则用来存储数组对象的长度。 AQS是队列同步器,是用来构建锁的基础框架,Lock实现类都是基于AQS实现的。AQS是基于模板方法模式进行设计的,所以锁的实现需要继承AQS并重写它指定的方法。AQS内部定义了一个FIFO的队列来实现线程的同步,同时还定义了同步状态来记录锁的信息。 加分回答 早期的synchronized性能较差,不如Lock。后来synchronized在实现上引入了锁升级机制,性能上已经不输给Lock了。所以,synchronized和Lock的区别主要不在性能上,因为二者性能相差无几。 Java 6为了减少获取锁和释放锁带来的性能消耗,引入了偏向锁和轻量级锁。所以,从Java 6开始,锁一共被分为4种状态,级别由低到高依次是:无锁、偏向锁、轻量级锁、重量级锁。随着线程竞争情况的升级,锁的状态会从无锁状态逐步升级到重量级锁状态。锁可以升级却不能降级,这种只能升不能降的策略,是为了提高效率。 synchronized的早期设计并不包含锁升级机制,那个时候只有无锁和有锁之分。是为了提升性能才引入了偏向锁和轻量级锁,所以需要重点关注这两种状态的原理,以及它们的区别。 偏向锁,顾名思义就是锁偏向于某一个线程。当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,以后该线程再进入和退出同步块时就不需要做加锁和解锁操作了,只需要简单地测试一下Mark Word里是否存储着自己的线程ID即可。 轻量级锁,就是加锁时JVM先在当前线程栈帧中创建用于存储锁记录的空间,并将Mark Word复制到锁记录中,官方称之为Displaced Mark Word。然后线程尝试以CAS方式将Mark Word替换为指向锁记录的指针,如果成功则当前线程获得锁,如果失败则表示其他线程竞争锁,此时当前线程就会通过自旋来尝试获取锁。

  • 32
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

羱滒

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值