AQS理解之三,由刚才写的锁转变成一个公平锁
在第二节里我们实现了一个不公平的锁,之所以说它不公平,主要是因为加锁后解锁时,阻塞的其他线程获取到的锁的可能是随机的,并不是按照顺序来确定的,如果要转变为公平锁,我们应该要记录这个进入的顺序,并在解锁时必须满足是第一个阻塞等待的线程才解锁。
其次,在上一节我们实现的锁是一个自旋阻塞等待的,这样的话线程还是在继续白白工作的,我们在这里也进行优化,使用LockSupport来优化。
我们定义一个链表来保存阻塞的线程,在取出时从链表中取出即可。
这里我们实现了一个简单的公平锁,用Node链表保存了阻塞的线程,在unlock时取出最先的一个进行解锁。
class MyLock{
private Thread ownerThread;
private volatile AtomicInteger state;
private boolean isFair;
private volatile Node head;
static class Node{
private volatile Node pre;
private volatile Node next;
private volatile Thread thread;
public Node(Thread thread){
this.thread = thread;
}
@Override
public String toString() {
return "Node{" +
", thread=" + thread==null?"null":thread.getName() +
'}';
}
}
public MyLock(boolean isFair){
state = new AtomicInteger(0);
head = new Node(null);
this.isFair = isFair;
}
public Thread getOwnerThread() {
return ownerThread;
}
public void setOwnerThread(Thread ownerThread) {
this.ownerThread = ownerThread;
}
public boolean lock(){
//可重入
for (;;) {
if (Thread.currentThread() == getOwnerThread()){
state.incrementAndGet();
return true;
}else if(state.compareAndSet(0,1)) {
System.out.println(Thread.currentThread());
setOwnerThread(Thread.currentThread());
return true;
}
if (isFair){
Node temp = head;
while(temp.next !=null){
temp = temp.next ;
}
//将阻塞的线程放入链表的队尾。
temp.next = new Node(Thread.currentThread());
temp.next.pre = temp;
LockSupport.park();
}
}
public void unlock(){
if (Thread.currentThread() != getOwnerThread()){
throw new RuntimeException("不是锁持有线程,不能解锁");
}
setOwnerThread(null);
state.decrementAndGet();
if(isFair){
Node temp = head;
Node unlockNode = head.next;
if (unlockNode!=null&&unlockNode.thread!=null){
LockSupport.unpark(unlockNode.thread);
temp.next = unlockNode.next;
if(unlockNode.next!=null){
unlockNode.next.pre = temp;
}
}
Node temp1 = head;
while (temp1!=null){
System.out.println("unlock"+temp1.thread);
temp1 = temp1.next;
}
}
}
}
但是需要注意,我们这里的代码是错误的,因为对head节点的操作位于多个线程,而代码里的操作不是原子性的,所以会有问题,所以我们可以用AtomicReference变量来存储,借助这个类来实现原子操作