1. 什么是AQS?作用是什么?
AQS(AbstractQueuedSynchronizer)AQS是一个用于构建锁和同步器的抽象的线程同步框架,用于解决线程阻塞和资源抢占的问题。
2. AQS的组成包含哪些内容?
1.state同步状态:用于记录资源的占用模式,即记录资源在独占模式和共享模式下什么时候占用锁。
2.CLH队列:是一个虚拟双向队列,用于存放暂时获取锁失败的线程,每个在队列里等待的线程被封装成一个Node节点。
3. AQS独占锁的获取流程
- 先直接获抢锁(第一次抢锁):线程调用lock()方法,首先通过CAS抢锁,如果state为0则抢锁成功,当前线程成功持锁;如果state为1则表示锁已经被其他线程持有,抢锁失败,抢锁失败则进入acquire(int arg)方法。
- 抢锁失败进入acqui()方法(第二次抢锁):一进入acquire(int arg)方法马上调用tryAcquire(arg) 方法再次进行抢锁,tryAcquire(arg)里是用nonfairTryAcquire方法(CAS)快速进行抢锁。
- 入队:如果返回true则表示抢锁成功,该线程成功持有锁;若返回false则抢锁失败,需要用addWaiter方法CAS自旋式将这个线程放入acquireQueued队列里。
- 排队与阻塞:这个线程放入acquireQueued队列里:
(1).首先如果在队列里是对头元素,直接获取锁,该线程成功持锁;
(2).若不是对头元素,则判断其前驱是否为对头元素,是则可以抢锁(只有头节点的后继才有资格尝试获取锁),直接调用tryAcquire方法抢锁,如果抢锁成功,则当前线程成功持锁;
(3).失败则线程阻塞,看看前驱节点的状态是否为可以唤醒后继节点状态(即SIGNAL=-1),如果是则等待被唤醒后得到抢锁资格继续抢锁;如果不是即前驱节点异常(即SIGNAL>-1)时,重新通过CAS自旋抢锁,直至抢到锁或者其前驱节点状态为SIGNAL=-1。
4. 共享模式和独占模式有什么区别?(多线程实现下)
独占模式:某一个操作只能给一个线程进行,线程A在进行时,别的线程只能阻塞。state就是用来判断该操作是否在被线程进行。
eg:线程A想要执行某一操作,若state=0,说明这个操作没有别的线程在执行,该线程可以执行这个操作,并且state会将0改成1,此时state=1;若此时线程B也想进行这个操作,但是state=1,则线程B就进行不了这个操作,会被阻塞。
共享模式:某一个操作能够给多个线程执行,线程A/B/C...等等,只要在共享名额(用户自己设置的)内,可以多线程一起操作。
eg:共享模式下需要限制线程共享名额(不限制系统会崩溃),用state来限制。首先先设置共享的数量n,所以state=n>0;线程A想要执行某一操作,state=n,说明这个操作还没有别的线程在执行,该线程可以执行这个操作,并且state会数量减一来表示又有1个线程进行该操作共享名额减一,所以state-1;若此时线程B也想进行这个操作,也是重复这个流程;直至有某个线程想进行这个操作,但是state=0,则表示共享名额为0,已经不能再共享这个操作了,这个线程就好被阻塞。
5. AQS的公平锁和非公平锁有什么区别?
公平锁和非公平锁最大的区别在于:在抢锁时是否排队。
公平锁:公平锁在线程抢锁时会遵循先进先出的排队原则排队,只有队头元素可以拿到锁,其余线程会被阻塞在外面,需要等待唤醒后才能再次尝试获取锁。
优点:公平锁这样保证了每个线程都能拿到锁,不会产生饥饿线程。
缺点:这样排队取锁的方式每个线程都会阻塞与唤醒,性能消耗太大。
非公平锁:非公平锁在线程抢锁时,先会插队抢锁(即一上来就先直接抢锁,不先排队),如果刚刚好有锁空闲了,可以直接强到并尺有锁;若一上来插队抢锁不成功,才会去排队。
优点:没有一上来就排队,而是插队抢锁,减少了阻塞和唤醒消耗的性能。
缺点:会存在饥饿线程的风险,因为有可能插队抢锁多次成功,导致在排队的线程一直拿不到锁。