Java并发并发 结合源码分析结合源码分析AQS原理原理
主要介绍了Java并发 结合源码分析AQS原理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
前言:前言:
如果说J.U.C包下的核心是什么?那我想答案只有一个就是AQS。那么AQS是什么呢?接下来让我们一起揭开AQS的神秘面纱
AQS是什么?是什么?
AQS是AbstractQueuedSynchronizer的简称。为什么说它是核心呢?是因为它提供了一个基于FIFO的队列和state变量来构建锁和其他同步装置的基础框架。下面是其底层的数据结构。
AQS的特点的特点
1、其内使用Node实现FIFO(FirstInFirstOut)队列。可用于构建锁或者其他同步装置的基础框架
2、且利用了一个int类表示状态。在AQS中维护了一个volatile int state,通常表示有线程访问资源的状态,当state>1的时候表示线程重入的数量,主要有三个方法控制:
getState(),setState(),CompareAndSetState()。后面的源码分析多用到这几个方法
3、使用方法是继承,子类通过继承并通过实现它的方法管理其状态(acquire和release)的方法操纵状态。
4、同时实现排它锁和共享锁模式。实际上AQS功能主要分为两类:独占(只有一个线程能执行)和共享(多个线程同时执行),它的子类要么使用独占功能要么使用共享功能,而ReentrantLock是通过
两个内部类来实现独占和共享
CountDownLatch如何借助如何借助AQS实现计数功能?实现计数功能?
先来说一下CountDownLatch,CountDownLatch是一个同步辅助类,通过它可以来完成类似阻塞当前线程的功能,即一个或多个线程一起等待,直到其他线程执行的操作完成。要实现上面的功
能,CountDownLatch是通过一个给定的原子操作的计数器来实现。调用该类的await()方法的线程会一直处于阻塞状态,直到其他线程调用countDown()方法使得计数器的值变为0之后线程才会执行,这
个计数器是不能被重置的。通常这个类会用在程序执行需要等待某个条件完成的场景,比如说并行计算,可将一个数据量很大的计算拆分成一个个子任务,当子任务完成之后,再将最终的结果汇总。每
次访问CountDownLatch只能有一个线程,但是这个线程在使用完countDown()方法之后能多个线程能继续运行,而调用await()方法的线程就一定要计数器为0才会运行
下面来分析CountDownLatch的源码以及如何使用AQS框架
public class CountDownLatch {
/**
* CountDownLatch 实现同步控制
* 底层是使用AQS的state来代表count
*/
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
//初始化内部类实际上是设置AQS的state
Sync(int count) {
setState(count);
}
int getCount() {
return getState();
}
//尝试获取共享是看当前的state是否为0
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
/*尝试释放共享锁则是递减计数直到state==0就返回false代表资源已经释放完全否则就会使用CAS来让state减一*/
protected boolean tryReleaseShared(int releases) {
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
private final Sync sync;
/**
* 初始化CountDownLatch,实际上是初始化内部类,实际上是设置AQS的state,count不能小于0
*/
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
/**
* 这里实际上是调用了AQS里的acquireSharedInterruptibly方法,完成的功能就是先去查看线程是否被中断,中断则抛出异常,没有被中断就会尝试获取共享资源。 * 注意在syn内部类中重写了tryAcquireShared,也就是当state为0就返回1,这时候就会将当前线程放入AQS的队列中去,也就是这时候线程可以不再阻塞而是尝试去获取锁
*/
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
/**
* 原理同上面方法,但是加了一个时间参数来设置等待的时间
*/
public boolean await(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
/**
* 这里传入参数为1,同样上面内部类一样重写了AQS的tryReleaseShared方法,使用这个重写的方法来让计数器原子操作的减一
*/
public void countDown() {
sync.releaseShared(1);
}
/**
* 就是获取AQS的state
*/
public long getCount() {
return sync.getCount();
}
/**
* 转换成字符串的方法
*/
public String toString() {
return super.toString() + "[Count = " + sync.getCount() + "]";
}
}
由上面代码可看见CountDownLatch实现了AQS的共享锁,原理是操作state来实现计数,并且重写了tryAcquireShared(),tryReleaseShared()等方法
Semaphore是如何借助是如何借助AQS实现控制并发访问线程个数?实现控制并发访问线程个数?
Semaphore的功能类似于操作系统的信号量,可以很方便的控制某个资源同时被几个线程访问,即做并发访问控制,与CountDownLatch类似,同样是实现获取和释放两个方法。Semaphore的使用场
景:常用于仅能提供访问的资源,比如数据库的连接数最大只有30,而应用程序的并发数可能远远大于30,这时候就可以使用Semaphore来控制同时访问的线程数。当Semaphore控制线程数到1的时候
就和我们单线程一样了。同样Semaphore说是信号量的意思,我们这里就可以把它理解为十字路口的红绿灯,可以控制车流量(这里是控制线程数)
下面来分析Semaphore的源码以及如何使用AQS框
public class Semaphore implements java.io.Serializable { private static final long serialVersionUID = -3222578661600680210L; /** 所有机制都通过AbstractQueuedSynchronizer子类实现 */