AQS原理简介以及利用AQS简单实现一个共享锁

  1. AQS
    指AbstractQueuedSynchronizer类。
    AQS是java中管理“锁”的抽象类,锁的许多公共方法都是在这个类中实现。AQS是独占锁(例如,ReentrantLock)和共享锁(例如,Semaphore)的公共父类。AQS也被称为队列同步器,是用来构建锁或者其他同步组件的基础框架。
    使用一个int成员变量表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作。
    主要使用方式就是继承,子类通过继承AQS并实现它的抽象方法来管理同步状态,对同步状态进行更改用到了AQS的getState setState和compareAndSetState方法。
  2. CLH队列(Craig, Landin, and Hagersten lock queue)
    CLH队列是AQS中“等待锁”的线程队列。在多线程中,为了保护竞争资源不被多个线程同时操作而起来错误,我们常常需要通过锁来保护这些资源。在独占锁中,竞争资源在一个时间点只能被一个线程锁访问;而其它线程则需要等待。CLH就是管理这些“等待锁”的线程的队列。
    CLH是一个非阻塞的 FIFO 队列。也就是说往里面插入或移除一个节点的时候,在并发条件下不会阻塞,而是通过自旋锁和 CAS 保证节点插入和移除的原子性。
    CLH队列图示
  3. Node简介
    Node是CLH队列的节点,代表“等待锁的线程队列”。
    (01) 每个Node都会一个线程对应。
    (02) 每个Node会通过prev和next分别指向上一个节点和下一个节点,这分别代表上一个等待线程和下一个等待线程。
    (03) Node通过waitStatus保存线程的等待状态。
    (04) Node通过nextWaiter来区分线程是“独占锁”线程还是“共享锁”线程。如果是“独占锁”线程,则nextWaiter的值为EXCLUSIVE;如果是“共享锁”线程,则nextWaiter的值是SHARED。
  4. 流程图简单描述AQS的原理
    一个线程获取同步状态,若能够获取锁便执行;
    若获取不到,便将此线程封装成一个node节点,加入CLH队列尾部。若节点的前驱为头节点(头节点为正在运行的节点),尝试获取锁,若成功,便将此节点设置为前驱节点,并执行;否则继续等待。

在这里插入图片描述

  1. 使用AQS实现共享锁的思路
  • acquireShared

此方法是共享模式下线程获取共享资源的顶层入口。它会获取指定量的资源,获取成功则直接返回,获取失败则进入等待队列,直到获取到资源为止,整个过程忽略中断

public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}

这里tryAcquireShared()需要自定义同步器去实现。但是AQS已经把其返回值的语义定义好了:负值代表获取失败;0代表获取成功,但没有剩余资源;正数表示获取成功,还有剩余资源,其他线程还可以去获取。所以这里acquireShared()的流程就是:
(1)tryAcquireShared()尝试获取资源,成功则直接返回;
(2)失败则通过doAcquireShared()进入等待队列,直到获取到资源为止才返回。

  • releaseShared
    此方法是共享模式下线程释放共享资源的顶层入口。它会释放指定量的资源,如果成功释放且允许唤醒等待线程,它会唤醒等待队列里的其他线程来获取资源。
  public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }

tryReleaseShared是需要我们自己实现的方法,如果tryReleaseShared成功的话就执行doReleaseShared唤醒下一个线程。



private void doReleaseShared() {
    for (;;) {
        Node h = head;
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            if (ws == Node.SIGNAL) {
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;
                unparkSuccessor(h);//唤醒后继
            }
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;
        }
        if (h == head)// head发生变化
            break;
    }
}
  1. 使用AQS实现共享锁代码实现
public class AQSSharedLock implements Lock {
	private final Sync sync = new Sync(3);

	private static final class Sync extends AbstractQueuedSynchronizer {
		private static final long serialVersionUID = -8540764104913403569L;

		Sync(int count) {
			if (count <= 0) {
				throw new IllegalArgumentException("锁资源数不能为负数~");
			}
			setState(count);
		}

		@Override
		public int tryAcquireShared(int reduceCount) {
			for (;;) {
				int current = getState();
				int newCount = current - reduceCount;
				if (newCount < 0 || compareAndSetState(current, newCount)) {
					return newCount;
				}
			}
		}

		@Override
		public boolean tryReleaseShared(int returnCount) {
			for (;;) {
				int current = getState();
				int newState = current + returnCount;
				if (compareAndSetState(current, newState)) {
					return true;
				}
			}
		}
	}

	@Override
	public void lock() {
		sync.acquireShared(1);
	}

	@Override
	public void unlock() {
		sync.releaseShared(1);
	}

	@Override
	public boolean tryLock() {
		return false;
	}

	@Override
	public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
		return false;
	}

	@Override
	public void lockInterruptibly() throws InterruptedException {

	}

	@Override
	public Condition newCondition() {
		return null;
	}
}

我们重写了tryAcquireShared和tryReleaseShared,通过自旋锁来保证这两个操作的原子性,然后重写了锁的lock和unlock方法,调用acquireShared和releaseShared完成锁的枷锁以及级锁操作。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值