代码改变世界

AQS原理

2022-12-06 18:05  杭伟  阅读(87)  评论(0编辑  收藏  举报

*前置知识:

1,线程的生命周期:新建(new())--就绪(start())--运行(run())--阻塞--死亡;

线程的非运行状态: 等待,挂起,睡眠,阻塞 

正常生命周期中运行的线程,如果有同步需求,假如正在竞争一把锁,在没拿到锁时,应该被挂起。(而不是什么都不做)

2,在队列中的线程,应该处于阻塞状态,并在必要的时候被唤醒。线程的挂起和唤醒会引发线程用户态和内核态的切换。

 

一个线程管理框架的要点:

1,通用,底层通用,与上层业务解耦;

2,利用CAS,原子修改共享标记位;

3,等待队列(阻碍其它线程);

AQS(线程同步队列)基本实现原理:

 

state标记位

 

Node的结构:{

线程对象

等待状态(4个枚举值:CANCELED,SIGNAL,CONDITION,PROPAGATE)

前、后指针

}

 

入队

1,尝试获取锁(修改标记位),立即返回;tryAcquire

2,获取锁(修改标记位),愿意进入等待队列,直到获取。acquire

tryAcquire --> 上层业务逻辑;不想等待锁,自己处理;

    protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }

选择等待锁,调用acquire方法

    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

 

addWaiter方法(),将当前线程封装成队列节点,模式为exclusive,即独占;插入到队尾;

 1     /**
 2      * Creates and enqueues node for current thread and given mode.
 3      *
 4      * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
 5      * @return the new node
 6      */
 7     private Node addWaiter(Node mode) {
 8         Node node = new Node(Thread.currentThread(), mode);
 9         // Try the fast path of enq; backup to full enq on failure
10         Node pred = tail;
11         if (pred != null) {
12             node.prev = pred;
13             if (compareAndSetTail(pred, node)) {
14                 pred.next = node;
15                 return node;
16             }
17         }
18         enq(node);
19         return node;
20     }
View Code

 

acquireQueued()方法,对队列中的线程进行挂起

LockSupport.park(this);

 挂起当前线程的条件:

 

 

 

AQS的双向队列头节点只是一个占位,并不是真正的线程节点。

curr.pre == head  判断当前节点的前置节点是不是头节点,是的话当前节点才能拿锁(当前节点位于队头)

 

*始终保证head之后只有一个节点在通过CAS在拿锁,其它线程对象都应该被挂起。 

 

 

 设计要点:

始终保证队列里只有一个线程在自旋获取锁,其它线程都是已经被挂起或者正在被挂起;

 

出队

出队则唤醒线程:

tryRelease()

release()

唤醒线程,LockSupport.unpark(thread);

同时,将全局volatile变量State的值设置为0

 

 

上面是同步队列,还有一个条件队列。

 

 

*AQS哪些地方在自旋?

可以通过看ReentrantLock源码辅助看AQS源码。