Java中的线程进阶:AQS机制和ReentrantLock

一、AQS

1. 基本概念

  • AQS:全称是AbstractQueuedSynchronizer,是阻塞式锁和相关的同步器工具的框架。AQS核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。

CLH(Craig,Landin and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配。

2. 特点

在这里插入图片描述

  • state属性来表示资源的状态(分独占模式和共享模式),子类需要定义如何维护这个状态,控制如何通过该属性获取锁和释放锁。
  • 一般用getState来获取state状态,用setState来设置state状态。还可以用compareAndSetState来使用CAS机制设置state状态。
  • 独占模式是只有一个线程能够访问资源,而共享模式可以允许多个线程访问资源。
  • 提供了基于FIFO的等待队列,类似于Monitor的EntryList。
  • 条件变量来实现等待、唤醒机制,支持多个条件变量,类似于Monitor的WaitSet。

3. 细节描述

3.1 AQS的子类

AQS的子类主要实现了以下的方法:① tryAcquire ② tryRelease ③ tryAcquireShared ④ tryReleaseShared ⑥ isHeldExclusively。如果子类不实现而直接调用该方法,则会抛出异常。

3.2 获取锁和释放锁

AQS中暂停和恢复线程实际上使用的是parkunpark机制。

  • 获取锁的姿势
    // 如果获取锁失败
    if (!tryAcquire(arg)) {
     // 入队, 可以选择阻塞当前线程
    }
    
  • 释放锁的姿势
    // 如果释放锁成功
    if (tryRelease(arg)) {
     // 让阻塞线程恢复运行
    }
    

4. AQS自定义不可重入锁

class MyLock implements Lock {

    //独占锁,同步器类
    class MySync extends AbstractQueuedSynchronizer{
        @Override
        protected boolean tryAcquire(int arg) {
            if(compareAndSetState(0, 1)){
                //如果加锁成功,则设置owner为当前线程
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        @Override
        protected boolean tryRelease(int arg) {
            setExclusiveOwnerThread(null);
            setState(0);//注意先后顺序,应该把对state的设置放在后面,利用state的volatile的写屏障,保证之前的变量也是对其他线程可见的
            return true;//只有加了锁的线程才能解锁,所以操作必定成功
        }

        @Override
        protected boolean isHeldExclusively() {//是否独占锁
            return getState() == 1;
        }

        public Condition newCondition(){
            return new ConditionObject();
        }
    }

    private MySync sync;

    @Override
    public void lock() {
        sync.acquire(1);//acquire调用tryAcquire
    }

    @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() {
        sync.release(1);//release方法除了调用tryRelease方法,还会唤醒阻塞的线程
    }

    @Override
    public Condition newCondition() {
        return sync.newCondition();
    }
}

二、ReentrantLock

在这里插入图片描述

1. 非公平锁

1.1 加锁成功流程

  1. 没有竞争时,线程Thread-0到来即可获取该锁,即加锁成功。
    在这里插入图片描述
    final void lock() {
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }
    

1.2 加锁失败流程

  1. 当Thread-0获取该锁之后,Thread-1到来,此时,尝试利用CAS将state由0改为1,结果失败。
    在这里插入图片描述
  2. 由此,则进入了acquire逻辑中的tryAcquire逻辑,tryAcquire逻辑中会再次尝试加锁一次,但此时state已经是1,结果仍然失败。
    public final void acquire(int arg) {
    	if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)){
    		selfInterrupt();  
    	}
    }
    
  3. 接下来进入addWaiter逻辑,构造Node队列来存放阻塞的线程。① 图中黄色三角表示该Node的waitStatus状态,其中0为默认正常状态。② Node的创建是懒惰的。③ 其中第一个Node称为Dummy(哑元)或哨兵,用来占位,并不关联线程。
    在这里插入图片描述
    注意上图中head就是Dummy,它也是Node(null),tail就是Node(Thread-1),addWaiter逻辑中完成的逻辑实际上就是将新进入阻塞队列的线程放在tail的位置。即如下的情况
    在这里插入图片描述
  4. 随后,当前线程进入acquireQueued逻辑,acquireQueued会在一个死循环中不断尝试获得锁,失败后进入park阻塞。逻辑如下:① 如果自己是紧邻着head(排第二位),那么再次tryAcquire尝试获取锁,当然这时state仍为1,失败。 ② 进入shouldParkAfterFailedAcquire逻辑,将前驱node,即head的waitStatus改为 -1,这次返回false,不会执行parkAndCheckInterrupt逻辑进入阻塞。注意,-1表示由该节点唤醒该节点之后的节点内的线程。③ 由于死循环,再次进入tryAcquire尝试获取锁,当然这时state仍为1,失败。 ④ 当再次进入shouldParkAfterFailedAcquire时,这时因为其前驱node的waitStatus已经是 -1,这次返回true ⑥ 进入parkAndCheckInterrupt逻辑, Thread-1 park(灰色表示)
    在这里插入图片描述
    在这里插入图片描述
    final boolean acquireQueued(final Node node, int arg) {
    	boolean failed = true;
    	try {
    		boolean interrupted = false;
    		for (;;) {
    			final Node p = node.predecessor();
    			if (p == head && tryAcquire(arg)) {
    				setHead(node);
    				p.next = null; // help GC
    				failed = false;
    				return interrupted;
    			}
    			if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()){
    				interrupted = true;
    			}
    		}
    	} finally {
    		if (failed){
    			cancelAcquire(node);
    		}
    	}
    }
    
  5. 当多个线程竞争失败之后,队列中变成如下的情况
    在这里插入图片描述

1.3 解锁成功流程

  1. Thread-0进入release逻辑释放锁,逻辑如下:① 进入tryRelease流程,设置exclusiveOwnerThread为null,同时将state设置为0。②此时head为null且waitStatus为-1,满足条件则进入unparkSuccessor逻辑,将队列中离head最近的下一个未取消节点的线程唤醒。本例中为Thread-1。
    在这里插入图片描述
    public void unlock() {
    	sync.release(1);
    }
    
    public final boolean release(int arg) {
    	if (tryRelease(arg)) {
    		Node h = head;
    		if (h != null && h.waitStatus != 0){
    			unparkSuccessor(h);
    		}
    		return true;
    	}
    	return false;
    }
    
    protected final boolean tryRelease(int releases) {
        int c = getState() - releases;
        if (Thread.currentThread() != getExclusiveOwnerThread()){
    		throw new IllegalMonitorStateException();
    	}
        boolean free = false;
        if (c == 0) {
            free = true;
            setExclusiveOwnerThread(null);
        }
        setState(c);
        return free;
    }
    
  2. Thread-1从阻塞(parkAndCheckInterrupt)中出来以后,如果没有其他线程竞争,则Thread-1在for死循环中将exclusiveOwnerThread修改为Thread-1,将state修改为1,加锁成功。同时,还会将Thread-1节点设置为头节点(涵盖了将当前节点内的Thread-1变成null,prev变成null,head变成该node的操作),并断开和原头节点的连接。
    在这里插入图片描述
    final boolean acquireQueued(final Node node, int arg) {
    	boolean failed = true;
    	try {
    		boolean interrupted = false;
    		for (;;) {
    			final Node p = node.predecessor();
    			if (p == head && tryAcquire(arg)) {
    				setHead(node);
    				p.next = null; // help GC
    				failed = false;
    				return interrupted;
    			}
    			if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()){
    				interrupted = true;
    			}
    		}
    	} finally {
    		if (failed){
    			cancelAcquire(node);
    		}
    	}
    }
    
    private void setHead(Node node) {
        head = node;
        node.thread = null;
        node.prev = null;
    }
    

1.4 解锁失败流程

  1. 如果这时候有其它线程来竞争(非公平的体现),例如这时有 Thread-4来了,如果不巧又被 Thread-4占了先Thread-4被设置为 exclusiveOwnerThread,state = 1,则Thread-1再次重复加锁失败的流程,即进入acquireQueued流程,获取锁失败,重新进入park阻塞。
    在这里插入图片描述

2. 可重入原理

ReentrantLock可重入锁住就是利用state控制锁的重入次数

  1. 加锁时,每重入一次,则state加1。
    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);
    		return true;
    	}
    	return false;
    }
    
  2. 解锁时,每解一次state减1,直到state减到0,才真正的解开了锁。
    // Sync 继承过来的方法, 方便阅读, 放在此处
    protected final boolean tryRelease(int releases) {
    	// state-- 
    	int c = getState() - releases;
    	if (Thread.currentThread() != getExclusiveOwnerThread()){
    		throw new IllegalMonitorStateException();
    	}
    	boolean free = false;
    	// 支持锁重入, 只有 state 减为 0, 才释放成功
    	if (c == 0) {
    		free = true;
    		setExclusiveOwnerThread(null);
    	}
    	setState(c);
    	return free;
    }
    

3. 可打断原理

3.1 不可打断原理

只有成功的获取锁以后,才可以返回打断状态。

private final boolean parkAndCheckInterrupt() {
	// 如果打断标记已经是 true, 则 park 会失效
	LockSupport.park(this);
	// interrupted 会清除打断标记
	return Thread.interrupted();
}

final boolean acquireQueued(final Node node, int arg) {
	boolean failed = true;
	try {
		boolean interrupted = false;
		for (;;) {
			final Node p = node.predecessor();
			if (p == head && tryAcquire(arg)) {
				setHead(node);
				p.next = null;
				failed = false;
				// 还是需要获得锁后, 才能返回打断状态
				return interrupted;
			}
			if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) {
				// 如果是因为 interrupt 被唤醒, 返回打断状态为 true
				interrupted = true;
			}
		}
	} finally {
	if (failed)
		cancelAcquire(node);
	}
}
 
public final void acquire(int arg) {
	if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) {
		// 如果打断状态为 true
		selfInterrupt();
	}
}
 
static void selfInterrupt() {
	// 重新产生一次中断
	Thread.currentThread().interrupt();
}

3.2 可打断原理

没有成功的获取锁以后,被打断就会直接抛出异常

public final void acquireInterruptibly(int arg) throws InterruptedException {
	if (Thread.interrupted()){
		throw new InterruptedException();
	}
	// 如果没有获得到锁, 进入 ㈠
	if (!tryAcquire(arg)){
		doAcquireInterruptibly(arg);
	}
}

// ㈠ 可打断的获取锁流程
private void doAcquireInterruptibly(int arg) throws InterruptedException {
	final Node node = addWaiter(Node.EXCLUSIVE);
	boolean failed = true;
	try {
		for (;;) {
			final Node p = node.predecessor();
			if (p == head && tryAcquire(arg)) {
				setHead(node);
				p.next = null; // help GC
				failed = false;
				return;
			}
			if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) {
			// 在 park 过程中如果被 interrupt 会进入此
			// 这时候抛出异常, 而不会再次进入 for (;;)
				throw new InterruptedException();
			}
		}
	} finally {
		if (failed){
			cancelAcquire(node);
		}
	}
}

4. 公平锁

公平锁与非公平锁的区别主要在于tryAcquire逻辑,对于非公平锁,新到的线程(非队列中的线程)不会管AQS队列中是否存在先到的线程,直接开抢。但是公平锁则不同,它会先判断队列中是否有先到的线程,如果有,自己不能抢。

// 与非公平锁主要区别在于 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;
}

// ㈠ AQS 继承过来的方法, 方便阅读, 放在此处
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()
	);
}

5. 条件变量

前言

  • 每个条件变量其实就对应着一个等待队列,其实现类是ConditionObject。

5.1 await流程

public final void awaitUninterruptibly() {
    Node node = addConditionWaiter();
    int savedState = fullyRelease(node);
    boolean interrupted = false;
    while (!isOnSyncQueue(node)) {
        LockSupport.park(this);
        if (Thread.interrupted()){
             interrupted = true;
        }
    }
    if (acquireQueued(node, savedState) || interrupted){
        selfInterrupt();
    }
}
  1. 开始Thread-0持有锁。
    在这里插入图片描述
  2. 调用await,进入ConditionObject的addConditionWaiter流程,此时会创建新的Node,其状态设为-2(Node.CONDITION),该Node关联Thread-0,加入等待队列尾部。注意,这个Node(Thread-0)即是firstWaiter也是lastWaiter。
    在这里插入图片描述
    private Node addConditionWaiter() {
        Node t = lastWaiter;
        // If lastWaiter is cancelled, clean out.
        if (t != null && t.waitStatus != Node.CONDITION) {
            unlinkCancelledWaiters();
            t = lastWaiter;
        }
        Node node = new Node(Thread.currentThread(), Node.CONDITION);
        if (t == null)
            firstWaiter = node;
        else
            t.nextWaiter = node;
        lastWaiter = node;
        return node;
    }
    
  3. 接下来进入AQS的fullyRelease流程,其内部逻辑如下:① 调用了release方法,释放同步器上的所有锁(为了有重入锁的时候,全部释放)。② 在release方法中,唤醒下一个阻塞的节点。
    在这里插入图片描述
    final int fullyRelease(Node node) {
        boolean failed = true;
        try {
            int savedState = getState();
            if (release(savedState)) {
                failed = false;
                return savedState;
            } else {
                throw new IllegalMonitorStateException();
            }
        } finally {
            if (failed)
                node.waitStatus = Node.CANCELLED;
        }
    }
    
    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
    
  4. 下一个节点竞争锁,假设没有其他竞争线程,那么Thread-1竞争成功。
    在这里插入图片描述
    final boolean acquireQueued(final Node node, int arg) {
    	boolean failed = true;
    	try {
    		boolean interrupted = false;
    		for (;;) {
    			final Node p = node.predecessor();
    			if (p == head && tryAcquire(arg)) {
    				setHead(node);
    				p.next = null; // help GC
    				failed = false;
    				return interrupted;
    			}
    			if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()){
    				interrupted = true;
    			}
    		}
    	} finally {
    		if (failed){
    			cancelAcquire(node);
    		}
    	}
    }
    
  5. 调用park方法使当前线程(本例中的Thread-0)陷入阻塞。
    在这里插入图片描述

5.2 signal流程

public final void signal() {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    if (first != null)
        doSignal(first);
}
  1. 假设当前的Thread-1要唤醒Thread-0。
    在这里插入图片描述

  2. 进入ConditionObject的第一个Node的doSignal流程。该流程逻辑如下:① 将当前的Node从链表中断开 ② 进入transferForSignal逻辑,将该断开的节点的waitStatus状态设置为0,转入到竞争锁的链表的尾部,并将前一个节点的waitStatus状态设置为-1。
    在这里插入图片描述
    在这里插入图片描述

    private void doSignal(Node first) {
       do {
            if ( (firstWaiter = first.nextWaiter) == null)
                lastWaiter = null;
            first.nextWaiter = null;
        } while (!transferForSignal(first) && (first = firstWaiter) != null);
    }
    
    final boolean transferForSignal(Node node) {
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;
        Node p = enq(node);
        int ws = p.waitStatus;
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true;
    }
    

三、Semaphore(暂时忽略)

四、CountdownLatch(暂时忽略)

五、CyclicBarrier(暂时忽略)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值