Java多线程-AQS

1、AQS是什么

有协作功能的一些类,如ReentrantLock和Semaphore,CountDownLatch、ReentrantReadWriteLock,它们底层都实现了一个基类,这就是AQS

AQS可以理解为是一个工具类,帮助我们实现线程之间的协作

2、AQS演示

public class Semaphore implements java.io.Serializable {
    private static final long serialVersionUID = -3222578661600680210L;
    private final Sync sync;


    abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 1192457210091910933L;

        Sync(int permits) {
            setState(permits);
        }

        final int getPermits() {
            return getState();
        }

        final int nonfairTryAcquireShared(int acquires) {
            for (;;) {
                int available = getState();
                int remaining = available - acquires;
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
        }
}

都是一个Sync内部类,去继承我们的AbstractQueuedSynchronizer(AQS)类,我们其他的类也是这个套路

3、AQS原理

AQS由三个核心部分组成:

  • State
  • 控制线程强锁和配合的FIFO队列
  • 期望协作工具类去实现的获取/释放等重要的方法

3.1、State

state的具体含义,会根据具体实现类的不同而不同,比如在Semaphore中,他表示“剩余的许可证的数量”,而在CountDownLatch中,它表示“还剩余倒数的数

state是volatile修饰的,会被并发地修改,所以素有修改state的方法都需要保证线程安全,比如getState,setState已经compareAndSetState操作来读取和更新这个状态,这些方法都是依赖于atomic包的支持

    //通过unsafe顶层指令来保证原子性
    protected final boolean compareAndSetState(int expect, int update) {
        // See below for intrinsics setup to support this
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

在ReentrantLock中,state是用来表示锁的占有情况,包括可重入计数,当state的值为0的时候,标识该Lock不被任何线程锁占有

3.2、FIFO队列

这个队列是用来存放“等待的线程”,AQS就是“排队管理员”,当多个线程竞争锁的时候,只有一个线程可以获取,其他的线程串在一起进行等待。当锁释放时,锁管理器就会挑选一个合适的线程来占有刚刚释放的锁

ASQ会维护一个等待的线程队列,把线程都放到这个队列中,且这个队列是双向链表

3.3、获取/释放的方法

这个需要实现类自己实现,会依赖state的值,经常会阻塞(获取不到锁的时候)

3.4、AQS的实现类

  1. 写一个类,想好协作的逻辑,实现获取/释放的方法
  2. 内部写一个Sync类继承AbstractQueuedSynchronizer
  3. 根据是否独占重写tryAcquirte/tryRelease或tryAcquireShared(int acquires)和tryReleaseShared(int releases)等方法,在之前写的获取/释放方法中调用AQS的acquire/release或者Shared方法

4、CountDownLatch源码分析

构造方法

    //构造方法
    public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }
    //调用sync的构造
    Sync(int count) {
        etState(count);
    }
    //设置state值
    protected final void setState(int newState) {
        state = newState;
    }

getCount方法

    //调用getCount方法
    public long getCount() {
        return sync.getCount();
    }
    int getCount() {
        return getState();
    }
    //最终返回的是state 的值
    protected final int getState() {
        return state;
    }

countDown方法

public void countDown() {
	sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
	if (tryReleaseShared(arg)) {
		//唤醒队列中的所有线程
		doReleaseShared();
		return true;
	}
	return false;
}
protected boolean tryReleaseShared(int releases) {
	//死循环
	for (;;) {
		//获取到state的值
		int c = getState();
		if (c == 0)
			return false;
		//如果不为0,说明还可以减
		int nextc = c-1;
		//用原子操作保证线程安全
		if (compareAndSetState(c, nextc))
			return nextc == 0;
	}
}

await方法

public void await() throws InterruptedException {
	sync.acquireSharedInterruptibly(1);
}
public final void acquireSharedInterruptibly(int arg)
		throws InterruptedException {
	if (Thread.interrupted())
		throw new InterruptedException();
	//判断state是不是0
	if (tryAcquireShared(arg) < 0)
		//进入到阻塞队列中
		doAcquireSharedInterruptibly(arg);
}
protected int tryAcquireShared(int acquires) {
	return (getState() == 0) ? 1 : -1;
}
  • 调用CountDownLatch的await方法时,便会尝试获取“共享锁”,不过一开始是获取不到该锁的,于是线程被阻塞
  • 而“共享锁”可获取到的条件,就是“锁计数器”的值为0
  • 而“锁计数器”的初始值为count,每当一个线程调用该CountDownLatch对象的countDown()方法时,才将“锁计数器”-1
  • count个线程调用countDown()之后,“锁计数器”才为0,而前面提到的等待获取线程的共享锁的线程才能继续运行

5、Semaphore源码分析

获取许可证

public void acquire(int permits) throws InterruptedException {
	//判断需要获取的许可证个数不能小于0,否则抛出异常
	if (permits < 0) throw new IllegalArgumentException();
	sync.acquireSharedInterruptibly(permits);
}

public final void acquireSharedInterruptibly(int arg)
		throws InterruptedException {
	if (Thread.interrupted())
		throw new InterruptedException();
	if (tryAcquireShared(arg) < 0)
		//小于0,就去等待
		doAcquireSharedInterruptibly(arg);
}
protected int tryAcquireShared(int acquires) {
	return nonfairTryAcquireShared(acquires);
}
//返回值如何小于0,获取许可证失败
final int nonfairTryAcquireShared(int acquires) {
	//死循环自旋
	for (;;) {
		//getState()获取许可证数量
		int available = getState();
		//判断当前许可证-获取许可证的值
		int remaining = available - acquires;
		if (remaining < 0 ||
			//如果获取的值够,就会进行cas操作保证线程安全
			compareAndSetState(available, remaining))
			return remaining;
	}
}
  • 在Semaphore中,state表示许可证的剩余数量
  • 看tryAcquire方法,判断nonfairTryAcquireShared大于等于0的话,代表获取成功
  • 这里会先检查剩余的许可证数量够不够这次需要的,用减法来计算,如果不够,就返回负数,如果够了,就用cas操作自旋来改变state的状态,知道改变成功返回整数;期间如果被其他人修改了导致获取为负数,也会直接返回负数代表失败

释放许可证

public void release(int permits) {
	//判断释放的许可证,小于0抛出异常
	if (permits < 0) throw new IllegalArgumentException();
	sync.releaseShared(permits);
}
public void release(int permits) {
	if (permits < 0) throw new IllegalArgumentException();
	sync.releaseShared(permits);
}
protected final boolean tryReleaseShared(int releases) {
	//自旋
	for (;;) {
		int current = getState();
		int next = current + releases;
		//判断当前的state+需要释放的许可证不能大于最大允许数,大于就抛出异常
		if (next < current) // overflow
			throw new Error("Maximum permit count exceeded");
		//使用cas操作,对state进行设置
		if (compareAndSetState(current, next))
			return true;
	}
}

6、ReentrantLock源码分析

解锁

public void unlock() {
	sync.release(1);
}
public final boolean release(int arg) {
	//tryRelease()代表锁是否被释放了
	if (tryRelease(arg)) {
		Node h = head;
		if (h != null && h.waitStatus != 0)
			//唤醒后面的节点
			unparkSuccessor(h);
		return true;
	}
	return false;
}
protected final boolean tryRelease(int releases) {
	//getState()获取当前的次数,这个就是当前的次数减一
	int c = getState() - releases;
	//判断当前线程是不是持有锁的线程,不是就抛出异常
	if (Thread.currentThread() != getExclusiveOwnerThread())
		throw new IllegalMonitorStateException();
	boolean free = false;
	//当前次数减完一后,如果为0,就释放锁
	if (c == 0) {
		//free代表锁是不是自由的
		free = true;
		setExclusiveOwnerThread(null);
	}
	//不为0,则代表是重入过,只是state-1操作
	setState(c);
	return free;
}
  • 由于可重入的,所以state代表重入的次数,每次释放锁,先判断是不是当前持有锁的线程释放的,如果不是抛出异常;如果是的,重入的次数减一,如果减到0,就说明完全释放了,于是free就是true,并且将state设置为0

加锁

final void lock() {
	if (compareAndSetState(0, 1))
		//将当前的线程设置为持有锁的线程
		setExclusiveOwnerThread(Thread.currentThread());
	else
		//
		acquire(1);
}
public final void acquire(int arg) {
	if (!tryAcquire(arg) &&
		//addWaiter()添加到队列中
		acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
		selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
	final Thread current = Thread.currentThread();
	int c = getState();
	//等于0代表没有线程持有
	if (c == 0) {
		if (!hasQueuedPredecessors() &&
			//通过cas操作来获取到这把锁
			compareAndSetState(0, acquires)) {
			setExclusiveOwnerThread(current);
			return true;
		}
	}
	//我这个线程正好是持有这把锁,是一个重入的操作
	else if (current == getExclusiveOwnerThread()) {
		//重入的次数+1
		int nextc = c + acquires;
		//判断重入后的值是不是溢出
		if (nextc < 0)
			throw new Error("Maximum lock count exceeded");
		//将state值设置进去
		setState(nextc);
		return true;
	}
	//代表锁被其他线程持有,直接返回false
	return false;
}

7、尝试利用AQS实现自己的Latch门闩

一次性门闩

public class OneShotLatch {

    private final Sync sync = new Sync();

    //获取
    public void await(){
        sync.acquireShared(0);
    }

    //释放
    public void signal(){
        sync.releaseShared(0);
    }


    //不需要强制重写,是因为不确定我们需要重写哪些方法
    static class Sync extends AbstractQueuedSynchronizer {
        @Override
        protected int tryAcquireShared(int arg) {
            return (getState() == 1 ) ? 1 : -1;
        }

        @Override
        protected boolean tryReleaseShared(int arg) {
            setState(1);

            return true;
        }
    }

    public static void main(String[] args) {
        final OneShotLatch oneShotLatch = new OneShotLatch();
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "尝试获取门闩,获取失败那就等待");
                    oneShotLatch.await();
                    System.out.println( "开闸放行" +Thread.currentThread().getName() + "继续运行");
                }
            }).start();

        }
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        oneShotLatch.signal();

        new Thread(new Runnable() {
            public void run() {
                System.out.println(Thread.currentThread().getName()+"尝试获取latch,获取失败那就等待");
                oneShotLatch.await();
                System.out.println("开闸放行"+Thread.currentThread().getName()+"继续运行");
            }
        }).start();
    }
}

 

 

 

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值