并发编程——从源码看AQS

目录

         一、什么是AQS?

二、自定义的不可重入锁

自定义同步器

自定义锁

三、 Reentrantlock原理

Reentrantlock的结构图

非公平锁加锁

解锁 

非公平锁的体现

 可重入原理

公平锁和非公平锁的区别

条件变量实现原理


一、什么是AQS?

全称是 AbstractQueuedSynchronizer,是阻塞式锁和相关的同步器工具的框架

用 state 属性来表示资源的状态(分独占模式<如ReentrantLock>和共享模式<如Semaphore/CountDownLatch>),子类需要定义如何维护这个状态,控制如何获取锁和释放锁

getState - 获取 state 状态

setState - 设置 state 状态

compareAndSetState - cas 机制设置 state 状态

独占模式是只有一个线程能够访问资源,而共享模式可以允许多个线程访问资源

提供了基于 FIFO 的等待队列,类似于 Monitor 的 EntryList

条件变量来实现等待、唤醒机制,支持多个条件变量,类似于 Monitor 的 WaitSet

子类主要实现这样一些方法

tryAcquire 独占

tryRelease 独占

tryAcquireShared 共享

tryReleaseShared 共享

isHeldExclusively 该线程是否正在独占资源

接下来,通过自定义同步锁和Reentralock来进一步深入了解AQS到底是怎么实现的 

二、自定义的不可重入锁

  • 自定义同步器

  可以查阅ReentrantLock,内部有一个Sync继承了AbstractQueuedSynchronizer

final class MySync extends AbstractQueuedSynchronizer {
	
	@Override
	protected boolean tryAcquire(int acquires) {
        //1代表加锁
		if (acquires == 1){
		//在AQS类中该方法是对状态变量sate赋值
        //原子安全,防止多个线程同时占用该锁,
			if (compareAndSetState(0, 1)) {
				setExclusiveOwnerThread(Thread.currentThread());
				return true;
 			}
 		}
		return false;
 	}


	@Override
	protected boolean tryRelease(int acquires) {
	if(acquires == 1) {
		if(getState() == 0) {
			throw new IllegalMonitorStateException();
 	}
		setExclusiveOwnerThread(null);
		//因为只有持有锁的线程才会释放锁,所以不需要原子安全考虑
		setState(0);
		return true;
 	}
		return false;
 }


	protected Condition newCondition() {
		//ConditionObject()是AQS类的一个内部类
		return new ConditionObject();
 }
	@Override//是否持有独占锁
	protected boolean isHeldExclusively() {
	return getState() == 1;

 	}
}
  • 自定义锁

class MyLock implements Lock {
	static MySync sync = new MySync();
	@Override
	// 尝试,不成功,进入等待队列
	public void lock() {
        //底层会调用tryacquire(1)和其他一些方法
		sync.acquire(1);
 	}
	@Override
	// 尝试,不成功,进入等待队列,可打断
	public void lockInterruptibly() throws InterruptedException {
		sync.acquireInterruptibly(1);
 	}

	@Override
	// 尝试一次,不成功返回,不进入队列
	public boolean tryLock() {
		return sync.tryAcquire(1);
 	}
	@Override
	// 尝试,不成功,进入等待队列,有时限
	public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
		return sync.tryAcquireNanos(1, unit.toNanos(time));
 	}
	@Override
	// 释放锁
	public void unlock() {
        //底层会调用tryrelease(1)和其他一些方法
		sync.release(1);
 	}
	@Override
	// 生成条件变量
	public Condition newCondition() {
		return sync.newCondition();
 	}
}

可能看完有点懵逼,没事我们再来好好分析下Reentrantlock底层是如何实现的,再回来看会有重新的认识

三、 Reentrantlock原理

  • Reentrantlock的结构图

  • 非公平锁加锁

  先从看构造器,默认实现的是非公平锁,因为调用的是非公平的同步器,从结构图看出来他继承了Sync的一些方法进行了重写

public ReentrantLock() {
        //调用的是非公平的同步器
        sync = new NonfairSync();
    }

1、当没有竞争的时候

 

   看代码:

  先从Reentrantlock的Lock方法开始看

 public void lock() {
        sync.lock();
    }

 点击进去找到NonfairSync的lock方法实现

final void lock() {
            //首先开始锁的状态是0,当没有竞争的时候(没被其他线程修改过),这个条件会成立
            //state变成1
            if (compareAndSetState(0, 1))
                //将当前线程设置为锁的持有者
                setExclusiveOwnerThread(Thread.currentThread());
            else
                //如果锁的状态不是0了,修改不成功了,说明有竞争,进入这个方法
                acquire(1);
        }

2、第一个竞争出现时

看代码 

 出现竞争的情况进入acquire()方法

 public final void acquire(int arg) {
        //tryAcquire方法暂时不细说,他也是获取锁,在下面的可重入会提到
        //线程会再一次尝试获取锁,获取失败的话,返回fales,!fales就是true
        if (!tryAcquire(arg) &&
            //前面条件是ture,那就会执行这个判断
            //两个方法,一个acquireQueued,一个addWaiter
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

 3、进入同步队列逻辑

 先调用了addWaiter方法图中黄色三角表示该 Node 的 waitStatus 状态,其中 0 为默认正常状态Node 的创建是懒惰的其中第一个 Node 称为 Dummy(哑元)或哨兵,用来占位,并不关联线。head代表双向链表的头,tail代表双向链表的尾部,第一次进入addWaiter方法的时候,首先会先创建一个哨兵,并且将当前线程放到同步队列中,并把当前节点的地址给尾部。

接下来才会将node(Thread-1加入同步队列)

注:同步队列中的单向箭头代表的不是node的指向,而是代表地址相同 

看代码: 

private Node addWaiter(Node mode) {
        //将当前线程封装成为node对象
        Node node = new Node(Thread.currentThread(), mode);
        //tail是AQS同步队列队尾元素
        Node pred = tail;
        //如果队尾不是null证明同步队列有元素
        if (pred != null) {
            node.prev = pred;//node的prev指向队尾
            if (compareAndSetTail(pred, node)) {
                pred.next = node;//让队尾的next指向它
                return node;//返回封装当前线程的node
            }
        }
        
        enq(node);//如果不存在元素执行初始化,创建哨兵,并将当前节点加入同步队列
        return node;//返回该线程节点
    }
//上述pre,next不能说明方向

  enq方法

private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // 如果尾是空的
                if (compareAndSetHead(new Node()))//给head初始化一个节点
                    tail = head;//并让初始化的节点的地址给尾
            //创建完哨兵以后再次进入循环,会进入eles,将当前线程封装的节点加入同步队列
            } else {
                node.prev = t;//让当前节点的pre指向尾
                if (compareAndSetTail(t, node)) {//并让当前节点成为尾节点的地址
                    t.next = node;//尾节点的next指向当前节点
                    return t;
            //else里面的操作,因为头尾都是同一个地址,只需要操作尾即可,就出现上面的图了
                }
            }
        }
    }

 4、acquireQueued 逻辑

 1、acquireQueued 会在一个死循环中不断尝试获得锁,失败后进入 park 阻塞

 2、如果自己是紧邻着 head(排第二位),那么再次 tryAcquire 尝试获取锁,当然这时 state 仍为 1,失败

3、进入 shouldParkAfterFailedAcquire 逻辑,将前驱 node,即 head 的 waitStatus 改为 -1,这次返回 false

4. shouldParkAfterFailedAcquire 执行完毕回到 acquireQueued ,再次 tryAcquire 尝试获取锁,当然这时state 仍为 1,失败

5. 当再次进入 shouldParkAfterFailedAcquire 时,这时因为其前驱 node 的 waitStatus 已经是 -1,这次返回true

6. 进入 parkAndCheckInterrupt, Thread-1 park(灰色表示)

  

 看代码:

 acquireQueued方法

final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;//最开始打断状态是false
            for (;;) {
                //获得当前节点的前驱节点,此刻前驱节点就是哨兵(和头元素同地址)
                final Node p = node.predecessor();
  //判断P是不是等于头节点(哨兵),那么再一次尝试获取锁,这里说明只有是头哨兵后的第一个节点才配尝试
  //(这里等看到解锁再回来看)如果被阻塞唤醒以后,又回到了这里
                if (p == head && tryAcquire(arg)) {//阻塞唤醒后获取锁成功
                    setHead(node);//让当前节点变成哨兵
                    p.next = null; //断掉前哨兵和获取锁成功的线程的联系
                    failed = false;
                    return interrupted;// 还是需要获得锁后, 才能返回打断状态
                }
                //获取失败,或者不是老二进入shouldParkAfterFailedAcquire方法,
                //该方如果前驱节点是0会改成-1,返回fales
                //如果是前驱节点是-1会直接返回ture
                //1、很明显第一次进入,前驱节点是0,改成-1返回fales,再一次进入循环
                //2、再一次到达这以后,返回了ture,执行后面的parkAndCheckInterrupt()阻塞
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    // 如果是因为 interrupt 被唤醒才会执行这里, 设置打断状态为 ture
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

shouldParkAfterFailedAcquire 方法 

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;//获得前驱节点的waitStatus
        if (ws == Node.SIGNAL)//Node.SIGNAL是-1
         
            return true;
        if (ws > 0) {
            
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
          
            //将前趋节点的waitStatus改成-1
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

parkAndCheckInterrupt方法

private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);//park住当前线程
  //如果有其他线程来打断(不是unpark是打断),返回ture,并清空打断标记
  // 如果打断标记已经是 true, 则 park 会失效,下一次再来park就park不住
  //如果不是因为打断而是unpark则返回的是fales
        return Thread.interrupted();
    }


public final void acquire(int arg) {
if (
!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
 ) {
// 如果打断状态为 返回的是true,进入selfInterrupt,这就是不可打断模式
//如果是unpack返回的就是fales,不会执行下面的selfInterrupt
selfInterrupt();
 }
 }

static void selfInterrupt() {
// 重新产生一次中断
Thread.currentThread().interrupt();
 }
}

5、再次有多个线程经历上述过程竞争失败,变成这个样子

  • 解锁 

设置 exclusiveOwnerThread 为 null

state = 0

 当前队列不为 null,并且 head 的 waitStatus = -1,进入 unparkSuccessor 流程

找到队列中离 head 最近的一个 Node(没取消的),unpark 恢复其运行,本例中即为 Thread-1

回到 Thread-1 的 acquireQueued 流程的parkAndCheckInterrupt方法处

如果加锁成功(没有竞争),会设置

exclusiveOwnerThread 为 Thread-1,state = 1

head 指向刚刚 Thread-1 所在的 Node,该 Node 清空 Thread

原本的 head 因为从链表断开,而可被垃圾回收

看代码 

先找到unlock方法 

 public void unlock() {
        sync.release(1);
    }

看release方法

public final boolean release(int arg) {
        if (tryRelease(arg)) {//如果符合解锁条件
            Node h = head;
            //判断队列是不是不为空
            //判断哨兵的waitStatus是不是不等于0
            if (h != null && h.waitStatus != 0)
                //唤醒哨兵后一个元素
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

再点进去看tryRelease方法(和公平锁共用)

protected final boolean tryRelease(int releases) {
            int c = getState() - releases;//获得当前的状态是1-传入的1,c=0;
            //判断当前线程是否是持有锁的线程
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {//如果c=0;
                free = true;
                setExclusiveOwnerThread(null);//让锁没有持有者
            }
            setState(c);//设置锁的状态是0
            return free;//返回free即接锁成功与否
        }

 看unparkSuccessor方法

private void unparkSuccessor(Node node) {
        int ws = node.waitStatus;
        if (ws < 0)//判断哨兵的waitStatus是不是-1
            compareAndSetWaitStatus(node, ws, 0);
        Node s = node.next;//找到哨兵的下一个元素
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)//如果不是空,唤醒它,回到当前线程在锁阻塞的方法上
            LockSupport.unpark(s.thread);
    }
  • 非公平锁的体现

    如果此刻在刚好唤醒同步队列的thread1要设置为此刻的锁持有者时(正准备执行tryAcquire方法时)此时来了一个新的线程,把锁抢了,thread1又拿不到锁再次进入 acquireQueued 流程,获取锁失败,重新进入 park 阻塞

  •  可重入原理

这是Reentrantlock支持的方式

sync中的方法,非公平锁调用的tryAcqire最终层层调用的就是且只有这个方法

 非公平锁调用的tryAcqire最终层层调用的就是且只有这个方法,
final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            // 如果已经获得了锁, 线程还是当前线程, 表示发生了锁重入
            else if (current == getExclusiveOwnerThread()) {
                // state++
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);//设置锁的state数值
                return true;
            }
            return false;
        }

 protected final boolean tryRelease(int releases) {
            // state--
            int c = getState() - releases;
            //如果当前线程不是锁持有者抛出异常
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {//如果一直接锁c被剪到0,证明重入的锁被接解除完
                free = true;
                setExclusiveOwnerThread(null);//将锁的持有者社为空
            }
            setState(c);//设置当前锁状态
            return free;//如果解完,则返回ture,没解完返回fales
        }
  • 公平锁和非公平锁的区别

//非公平锁的lock是直接不管一进来就先抢锁
final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);//公用方法
        }

//而公平锁先直接调用的是这个acquire方法
final void lock() {
            acquire(1);//共用方法
        }

//进入这了以后
public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

//上面分析可重入原理已经知道非公平锁的tryAcquire方法了


//公平锁的tryAcquire
protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                // 先检查 AQS 队列中是否有前驱节点, 没有才去竞争
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    }

 public final boolean hasQueuedPredecessors() {
        Node t = tail; 
        Node h = head;
        Node s;
        // h != t 时表示队列中有 Node
        return h != t &&
            // (s = h.next) == null 表示队列中还有没有老二
            // 或者队列中老二线程不是此线程
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

//来看看公平锁的tryAcquire方法

总结:

即非公平锁在调用自己的lock的时候,直接先抢锁,而公平锁则把第一次抢锁放在了tryAcquire方法中,并在抢锁前先判断了同步队列里是不是有等待唤醒的线程抢锁

  • 条件变量实现原理

1、await()流程

开始 Thread-0 持有锁,调用 await,进入 ConditionObject 的 addConditionWaiter 流程创建新的 Node 状态为 -2(Node.CONDITION),关联 Thread-0,加入等待队列尾部

 接下来进入 AQS 的 fullyRelease 流程,释放同步器上的锁

 

  unpark AQS 队列中的下一个节点,竞争锁,假设没有其他竞争线程,那么 Thread-1 竞争成功 

park 阻塞 Thread-0

  

public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            //进入addConditionWaiter
            Node node = addConditionWaiter();
            //将节点线程上的所有锁全部释放掉,因为可能存在锁重入
            
            int savedState = fullyRelease(node);
            int interruptMode = 0;
            while (!isOnSyncQueue(node)) {
                //让后让当前调用await()的线程在Condition队列中等待了
                LockSupport.park(this);
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) 
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }


 private Node addConditionWaiter() {
            Node t = lastWaiter;
            if (t != null && t.waitStatus != Node.CONDITION) {
                unlinkCancelledWaiters();
                t = lastWaiter;
            }
            // 创建一个关联当前线程的新 Node, 添加至队列尾部,Node.CONDITION = -2
            Node node = new Node(Thread.currentThread(), Node.CONDITION);
            //如果是空,将把他作为firstNode
            if (t == null)
                firstWaiter = node;
            else
                //加到尾部
                t.nextWaiter = node;
            lastWaiter = node;
            return node;
        }


final int fullyRelease(Node node) {
        boolean failed = true;
        try {
            //获得锁的状态
            int savedState = getState();
            if (release(savedState)) {//一直清除节点锁的State值,让其变成0,然后会唤醒等待队列中的下一个节点
                failed = false;
                return savedState;
            } else {
                throw new IllegalMonitorStateException();
            }
        } finally {
            if (failed)
                node.waitStatus = Node.CANCELLED;
        }
    }

2、signal 流程 

假设 Thread-1 要来唤醒 Thread-0

 进入 ConditionObject 的 doSignal 流程,取得等待队列中第一个 Node,即 Thread-0 所在 Node

执行 transferForSignal 流程,将该 Node 加入 AQS 队列尾部,将 Thread-0 的 waitStatus 改为 0,Thread-3 的waitStatus 改为 -1

 

  Thread-1 释放锁,进入 unlock 流程

public final void signal() {
            //检查调用signal的线程是不是锁的持有者,否则抛出异常
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            //找第一个元素
            Node first = firstWaiter;
            //如果不为空
            if (first != null)
                doSignal(first);
        }


 private void doSignal(Node first) {
            do {
                if ( (firstWaiter = first.nextWaiter) == null)
                    lastWaiter = null;
                //将他断开和Condition队列的联系
                first.nextWaiter = null;
               //如果转移失败,则继续找下一个节点
                //转移失败可能是在Condition队列的元素被打断或者超时
            } while (!transferForSignal(first) &&
                     (first = firstWaiter) != null);
        }

 
 final boolean transferForSignal(Node node) {
        //设置当前节点的WaitStatus属性
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;
        //将断开联系的节点转移到同步队列里,返回的是原最后一个节点
        Node p = enq(node);
        int ws = p.waitStatus;
        //并将返回的节点的WaitStatus属性设置成为-1
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true;
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值