Java面试系列
1.AQS
AQS:抽象同步队列AbstractQueuedSynchronizer
AQS是一个FIFO的双向队列,队列元素的类型为Node。AQS里面包括Node节点、state变量、ConditionObject内部类(条件变量)
一个锁对应一个AQS阻塞队列,对应多个条件变量,每个条件变量有自己的一个条件队列。
①Node节点
Node节点中的thread变量用来存放进入AQS队列里面的线程
shared变量用来标记该线程是获取共享资源时被阻塞挂起后放入AQS队列的
exclusive变量用来标记线程是获取独占资源时被挂起后放入AQS队列的
waitStatus变量记录当前线程的等待状态,waitStatus可以为cancelled(线程被取消了)、signal(线程需要被唤醒)、condition(线程在条件队列里面等待)、propagate(释放共享资源时需要通知其他节点)
②ConditionObject类
ConditionObject用来结合锁实现线程同步的。
ConditionObject是条件变量,每个条件变量对应一个条件队列(单向 链表 队列),其用来存放调用条件变量的await()方法后被阻塞的线程。
1.当一个获得锁的线程调用await()方法时(必须先调用锁的lock()方法获取锁),在内部会构造一个类型为Node.CONDITION的node节点,然后将该节点插入 条件队列末尾,之后当前线程会释放获取的锁(修改锁对应的state变量的值),并被阻塞挂起。
2.当另外一个线程调用ConditionObject条件变量的signal方法时(必须先调用锁的lock()方法获取锁),在内部会把条件队列里面队头的一个线程节点从条件队 列里面移除并放入AQS的阻塞队列里面,然后激活这个线程。
③state变量
private volatile int state; //共享变量,使⽤volatile修饰保证线程可⻅性
线程同步的关键是对状态值state进行操作,根据state是否属于一个线程,操作state的方式分为独占方式和共享方式。
1.在独占方式下,获取锁与释放锁的流程如下
当一个线程调用acquire()方法获取独占资源时,会首先使用tryAcquire()方法尝试获取资源,具体是设置状态变量state的值,成功则直接返回,失败则将当前线 程封装为Node.EXCLUSIVE的Node节点后插入到AQS的阻塞队列的尾部,并调用LockSupport.park(this)方法挂起自己。
当一个线程调用release()方法时会尝试使用tryRelease()操作释放资源,这里是设置状态变量state的值,然后调用LockSupport.unpark(thread)方法激活AQS队 列里面被阻塞的一个线程(thread)。被激活的线程则使用tryAcquire()尝试,看当前状态变量state的值是否能满足自己的需要,满足则该线程被激活,然后继续 向下运行,否则还是会被放入AQS队列并挂起。
2.在共享方式下,获取与释放资源的流程如下
当一个线程调用acquireShared()方法获取共享资源时,会首先使用tryAcquireShared()方法尝试获取资源,具体是设置状态变量state的值,成功则直接返回, 失败则将当前线程封装为Node.SHARED的Node节点后插入到AQS的阻塞队列的尾部,并调用LockSupport.park(this)方法挂起自己。
当一个线程调用releaseShared()方法时会尝试使用tryReleaseShared()操作释放资源,这里是设置状态变量state的值,然后调用LockSupport.unpark(thread)方法激活AQS队列里面被阻塞的一个线程(thread)。被激活的线程则使用tryAcquireShared()尝试获取资源,具体是查看当前状态变量state的值是否能满足自己的 需要,满足则该线程被激活,然后继续向下运行,否则还是会被放入AQS队列并挂起。
②CAS
CAS:CompareAndSwap (比较并替换)
CAS 算法 的过程是:它包含3个参数CAS(V,E,N),其中V表示要更新的变量,E表示预期值,N表示新值。
仅当V值等于E值时,才会将V的值设置为N,如果V值和E值不同,说明已经有其他线程做了更新,则当前线程什么都不做。最后CAS返回当前V的真实值。
在多个线程同时使用CAS操作一个变量时,只有一个会胜出,并成功更新,其余均会失败。失败的线程不会被挂起,仅是被告知失败,并允许再次尝试,当然也允许失败的线程放弃操作。
CAS怎么保证修改的值可见?volatile关键字
volatile 关键字的主要作⽤就是保证变量的可⻅性然后还有⼀个作⽤是防⽌指令重 排序 。
当修改volatile变量时,JMM会把线程对应的工作内存中的共享变量值刷新到主内存中。
当读取volatile变量时,JMM会把该线程对应的工作内存置为无效,线程从主内存中读取共享变量值。
ABA问题:在CAS操作中有个经典的ABA问题?解决方式?(版本号、时间戳)
假如线程①使用CAS修改初始值为A的变量X,那么线程①会首先去获取当前变量X的值(为A),然后使用CAS操作尝试修改X的值为B,如果使用CAS操作成功了,程序运行也不一定是正确的。
在线程①获取变量X的值A后,在执行CAS前,线程②使用CAS修改了X的值为B,然后又使用CAS修改了变量X的值为A。
所以,线程①执行CAS时X的值是A,但是这个A已经不是线程①获取时的A了,这就是ABA问题。
ABA问题的产生是因为变量的状态值产生了环形转换。
避免ABA问题:使用版本号或时间戳。给每个变量的状态值配备一个时间戳或者版本号。
#Java面试##Java##学习路径#