问:1、AQS是什么?
2、CAS是什么?
AQS:AbstractQueuedSynchronizer
CAS :CompareAndSwap
重入锁和读写锁基于AQS和CAS实现。
AQS依赖同步队列(一个FIFO双向队列)来完成同步状态的管理。当前线程获取同步状态失败时,AQS会将当前线程以及等待状态等信息构造成一个节点(Node)并且将其加入到同步队列中,同时会阻塞当前线程,当同步状态释放时,会把首节点中的线程唤醒,使其再次尝试获取同步状态。
同步队列中的Node节点用来保存获取同步状态失败的线程引用。等待状态以及前驱和后继节点。
AbstractQueuedSynchronizer:
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
static final class Node{
volatile Node prev;
volatile Node next;
volatile Thread thread;
}
private transient volatile Node head;
private transient volatile Node tail;
}
Node是构成同步队列的基础,AQS拥有首节点(head)和尾节点(tail),没有成功获取同步状态的线程将会放入到队列的尾部。
addNode(Node node){
//cas
tail.setNext = node ;
tail = node ;
}
popNode(){
//cas
head = head.next();
}
添加尾节点的过程是使用CAS的实现的。
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
共享式获取与独占式获取的最主要区别在于同一时刻能否有多个线程同时获取到同步状态。通过调用acquireShared(int arg)方法可以共享式得获取同步状态。
在acquireShared(int arg)方法中,同步器调用tryAcquireShared(int arg)方法尝试获取同步状态,其返回值为int类型,当返回值大于0时,表示能够获取同步状态。因此,在共享式获取的自旋过程中,成功获取同步状态并且退出自旋的条件就是tryAcquireShared(int arg)方法返回值大于等于0。共享式释放同步状态状态是通过调用releaseShared(int arg)方法.
tryReleaseShared(int arg)方法必须确保同步状态线程安全释放。一般是通过循环和CAS来保证的。因为释放同步状态的操作会同时来自多个线程。
CountDownLatch、ReentrantReadWriteLock、Semaphore等都是共享式获取同步状态的。
获锁超时
long waitStart = System.nanoTime();
long deadTime = waitStart + 5*1000 ;
for(;;){
long now = System.nanoTime();
if(now <= deadTime){
if(head == newNode){
return true ;
}
}else{
return false ;
}
}
公平性
非公平锁(默认)
如果读操作发生的比较频繁,我们又没有提升写操作的优先级,那么就会产生“饥饿”现象。请求写操作的线程会一直阻塞,直到所有的读线程都从ReadWriteLock上解锁了。如果一直保证新线程的读操作权限,那么等待写操作的线程就会一直阻塞下去,结果就是发生“饥饿”。因此,只有当没有线程正在锁住ReadWriteLock进行写操作,且没有线程请求该锁准备执行写操作时,才能保证读操作继续。
按照上面的叙述,简单的实现出一个读/写锁,代码如下:
public class ReadWriteLock{
private int readers = 0;
private int writers = 0;
private int writeRequests = 0;
public synchronized void lockRead() throws InterruptedException{
while(writers > 0 || writeRequests > 0){
wait();
}
readers++;
}
public synchronized void unlockRead(){
readers--;
notifyAll();
}
public synchronized void lockWrite() throws InterruptedException{
writeRequests++;
while(readers > 0 || writers > 0){
wait();
}
writeRequests--;
writers++;
}
public synchronized void unlockWrite() throws InterruptedException{
writers--;
notifyAll();
}
}
上面代码不支持重入,可添加localvalue来实现重入。
公平锁
利用AQS的CLH队列,释放当前保持的锁(读锁或者写锁)时,优先为等待时间最长的那个写线程分配写入锁,当前前提是写线程的等待时间要比所有读线程的等待时间要长。同样一个线程持有写入锁或者有一个写线程已经在等待了,那么试图获取公平锁的(非重入)所有线程(包括读写线程)都将被阻塞,直到最先的写线程释放锁。如果读线程的等待时间比写线程的等待时间还有长,那么一旦上一个写线程释放锁,这一组读线程将获取锁
去除writeRequests