多线程面试--进阶篇2(Lock和Condition和AQL分析)

Lock和Condition和AQL分析

1.已经有了synchronized为什么还要Lock?

1.可以尝试非阻塞的获取锁
2.可以超时获取锁
3.可以在获取锁的过程中可以被中断

2.Lock的常用API

Lock(),
tryLock尝试非阻塞地获取锁
lockInterruptibly:可中断
unlock()

3.锁的可重入是什么?

可重入锁指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁(前提得是同一个对象或者class),这样的锁就叫做可重入锁
(如果递归的时候,没有锁的可重入,就会死锁)

4.公平和非公平锁是什么?

公平锁,先对锁发出获取请求的一定先获得,(公平锁的效率比非公平锁效率要低)。
非公平锁反之;

5.读写锁ReentrantReadWriteLock有什么用?

允许多个读线程同时进行,但是只允许一个写线程 (在读多写少场景,性能会有提升.)

6.Condition接口是什么,有何用处?

Condition对象是由Lock对象(调用Lock对象的newCondition()方法)创建出来的,
Condition定义了等待/通知两种类型的方法,
Condition接口通过和Lock配合来实现等待通知机制
通知方法:
是调用Condition的signal()方法
等待方法是
是调用Condition的await()方法

7.AQS(AbstractQueuedSynchronizer)的基本使用方法

同步器的主要使用方式是继承,子类通过继承同步器并实现它的抽象方法来管理同步状态。
可重写的方法有:
tryAcquire 独占锁获取
tryRelease 独占锁释放
tryAcquireShared 共享锁获取
tryReleaseShared 共享锁释放
isHeldExclusively 快速判断被线程独占
同步器的设计是基于模板方法模式

使用者需要继承同步器并重写指定的方法,随后将同步器组合在自定义同步组件的实现中,并调用同步器提供的模板方法,而这些模板方法将会调用使用者重写的方法。
对同步状态进行更改就需要使用同步器提供的3个方法
1.getState() 获取同步状态
2.setState 设置同步状态
3.compareAndSetState 原子的设置同步状态

8.LockSupport 是什么

LockSupport是用于提供park/unpark操作的,也是构建同步组件的基础工具,
LockSupport定义了一组以park开头的方法用来阻塞当前线程,
以及unpark(Thread thread)方法来唤醒一个被阻塞的线程。

9.同步队列

1.同步器依赖内部的同步队列(一个FIFO双向队列)来完成同步状态的管理,
2.当前线程获取同步状态失败时,同步器会将当前线程以及等待状态等信息构造成为一个节点(Node)并将其加入同步队列。
3.同步器拥有首节点(head)和尾节点(tail),没有成功获取同步状态的线程将会成为节点加入该队列的尾部。

9.独占式同步状态获取与释放

通过调用同步器的acquire(int arg)方法可以获取同步状态
其主要逻辑是:
1.首先调用自定义同步器实现的tryAcquire(int arg)方法,该方法保证线程安全的获取同步状态,
2.如果同步状态获取失败,则构造同步节点(独占式Node.EXCLUSIVE,同一时刻只能有一个线程成功获取同步状态)并通过addWaiter(Node node)方法将该节点加入到同步队列的尾部,
3.最后调用acquireQueued(Node node,int arg)方法,使得该节点以“死循环”的方式获取同步状态。
4.如果获取不到则阻塞节点中的线程,而被阻塞线程的唤醒主要依靠前驱节点的出队或阻塞线程被中断来实现。

10.共享式同步状态获取与释放

共享式获取与独占式获取最主要的区别在于:同一时刻能否有多个线程同时获取到同步状态。
在acquireShared(int arg)方法中,同步器调用tryAcquireShared(int arg)方法尝试获取同步状态,
tryAcquireShared(int arg)方法返回值为int类型,当返回值大于等于0时,表示能够获取到同步状态。
因此,在共享式获取的自旋过程中,成功获取到同步状态并退出自旋的条件就是tryAcquireShared(int arg)方法返回值大于等于0。

11.ReentrantLock的实现了解多少

重入锁ReentrantLock:指的是任意线程在获取到锁之后能够再次获取该锁而不会被锁所阻塞
实现该特性属性解决2个问题.
一是,当线程再次获取锁时,锁需要去识别获取锁的线程是否是当前占据锁的线程,如果是,则再次成功获取。
二是,锁的最终释放。
nonfairTryAcquire方法里实现问题一再次获取同步状态的处理逻辑:
通过判断当前线程是否是获取锁的线程来决定获取操作是否成功,
如果是获取锁的线程再次请求,则将同步状态值进行增加并返回true,
表示获取同步状态成功。
同步状态值表示锁被一个线程重复获取的次数。

如果该锁被获取了n次,那么前(n-1)次tryRelease(int releases)方法必须返回false,
而只有同步状态完全释放了,才能返回true。
该方法将同步状态是否为0作为最终释放的条件,
当同步状态为0时,将占有线程设置为null,并返回true,表示释放成功。

12.对ReentrantReadWriteLock实现的了解

实现读写锁的关键是读写锁的自定义同步器在同步状态上维护多个读线程和一个写线程的状态
如果在一个整型变量上维护多种状态,就一定需要“按位切割使用”这个变量,
读写锁将变量切分成了两个部分,高16位表示读,低16位表示写。
读状态是所有线程获取读锁次数的总和,而每个线程各自获取读锁的次数只能选择保存在ThreadLocal中,由线程自身维护。

13.对Condition实现的了解

等待队列是一个FIFO的队列

在队列中的每个节点都包含了一个线程引用,该线程就是在Condition对象上等待的线程,如果一个线程调用了Condition.await()方法,那么该线程将会释放锁并构造成节点加入等待队列并进入等待状态。

一个Condition包含一个等待队列,新增节点只需要将原有的尾节点nextWaiter指向它,并且更新尾节点即可。

调用Condition的signal()方法,将会唤醒在等待队列中等待时间最长的节点(首节点),在唤醒节点之前,会将节点移到同步队列中。
(调用该方法的前置条件是当前线程必须获取了锁,signal()方法会进行isHeldExclusively()检查,也就是当前线程必须是获取了锁的线程。)

接着获取等待队列的首节点,将其移动到同步队列并使用LockSupport唤醒节点中的线程。
通过调用同步器的enq(Node node)方法,使等待队列中的头节点线程安全地移动到同步队列。
(Condition的signalAll()方法,相当于对等待队列中的每个节点均执行一次signal()方法,效果就是将等待队列中所有节点全部移动到同步队列中,并唤醒每个节点的线程。)

发布了53 篇原创文章 · 获赞 42 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/qq_42815122/article/details/86377964