AQS:AbstractQueuedSynchronizer

AQS在java.util.concurrent.locks包下

java.util.concurrent.locks大大提高了并发性能,AQS被认为是J.U.C的核心

CountDownLatch(倒计时器)-->同步工具类,协调多个线程之间的同步。通常用来控制线程等待,它可以让某一个线程等待直到倒计时结束,再开始执行。用来控制一个或者多个线程等待多个线程。维护了一个计数器 cnt,每次调用 countDown() 方法会让计数器的值减 1,减到 0 的时候,那些因为调用 await() 方法而在等待的线程就会被唤醒。  Python  Condition !

 1 public class CountDownLatch{
 2 /**
 3 Synchronization control For CountDownLatch.
 4 Uses AQS state to represent count.
 5 */
 6      private static final class Sync extends
 7 AbstractQueuedSynchronizer {
 8     
 9     }
10 }

CyclicBarrier(循环栅栏)-->让一组线程到达一个屏障(同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。用来控制多个线程互相等待,只有当多个线程都到达时,这些线程才会继续执行。和 CountdownLatch 相似,都是通过维护计数器来实现的。线程执行 await() 方法之后计数器会减 1,并进行等待,直到计数器为 0,所有调用 await() 方法而在等待的线程才能继续执行。CyclicBarrier 和 CountdownLatch 的一个区别是,CyclicBarrier 的计数器通过调用 reset() 方法可以循环使用,所以它才叫做循环屏障。 Python Event !

CyclicBarrier 有两个构造函数,其中 parties 指示计数器的初始值,barrierAction 在所有线程都到达屏障的时候会执行一次。

Semaphore(信号量)-->Semaphore 类似于操作系统中的信号量,可以控制对互斥资源的访问线程数。synchronized和可重入锁都是一次只允许一个线程访问,信号量一次可允许多个线程访问

AQS是一个用来构建锁和同步器的框架,使用AQS能简单且高效地构造出应用广泛的大量的同步器,比如我们提到的ReentrantLock,Semaphore,其他的诸如ReentrantReadWriteLock,SynchronousQueue,FutureTask等等皆是基于AQS的。

AQS原理分析

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

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

AQS使用一个int成员变量来表示同步状态,通过内置的FIFO队列来完成获取资源线程的排队工作。AQS使用CAS对该同步状态进行原子操作实现对其值的修改。

private volatile int state;//共享变量,使用volatile修饰保证线程可见性

状态信息通过protected类型的getState、setState、compareAndSetState进行操作

 1 //返回同步状态的当前值
 2 protected final int getState(){
 3     return state;
 4 }
 5 
 6 //设置同步状态的值
 7 protected final void setState(int newState){
 8     state = newState;
 9 }
10 
11 //原子地(CAS操作)将同步状态值设置为给定值update如果当前同步状态的值等于expect(期望值)
12 protected final boolean compareAndSetState(int expect, int update){
13     return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
14 }

AQS对资源的共享方式

Exclusive(独占)-->只有一个线程能执行,如ReentrantLock。又可分为公平锁和非公平锁

    公平锁:按照线程在队列中的排队顺序,先到者先拿到锁

    非公平锁:当线程要获取锁时,无视队列顺序直接去抢锁

Share(共享)-->多个线程可同时执行,如Semaphore/CountDownLatch

不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源state的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在顶层实现好了

AQS底层使用了模板方法模式

同步器的设计是基于模板方法模式的,如果需要自定义同步器一般的方式是这样:

1.使用者继承AbstractQueuedSynchronized并重写指定的方法(对于共享资源state的获取和释放)

2.将AQS组合在自定义同步组件的实现中,并调用其模板方法,而这些模板方法会调用使用者重写的方法。

1 //AQS使用了模板方法模式,自定义同步器时需要重写下面几个AQS提供的模板方法:
2 
3 isHeldExclusively() //该线程是否正在独占资源,只有用到condition才需要去实现它
4 
5 tryAcquire(int) //独占方式,尝试获取资源,成功则返回true,失败false
6 tryRelease(int) //独占方式,尝试释放资源,成功则返回true,失败false
7 tryAcquiredShared(int) //共享方式,尝试获取资源。负数表示失败,0表示成功,但没有剩余可用资源,正数表示成功,且有剩余资源
8 tryReleaseShared(int) //共享方式,尝试释放资源,成功返回true,失败false

默认情况下,每个方法都抛出UnsupportedOperationException。这些方法的实现必须是内部线程安全的,并且通常应该简短而不是阻塞。AQS类中的其他方法都是final,所以无法被其他类使用,只有这几个方法可以被其他类使用

以ReentrantLock为例,state初始化为0,表示未锁定状态。A线程lock()时,会调用tryAcquire()独占该锁并将state+1。此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即释放锁)为止,其他线程才有机会获取该锁。当然,释放锁之前,A线程自己是可以重复获取此锁的,这就是可重入的概念。

以CountDownLatch为例,任务分为N个子线程去执行,state也初始化为N(注意N要与线程个数一致)。这N个子线程是并行执行的,每个子线程执行完后countDown()一次,state会CAS(Compare and Swap)减1。等到所有子线程都执行完后(即state=0)会unpark()主调用线程,然后主调用线程就会从await()函数返回,继续后续动作

JUC其他组件

FutureTask:在介绍 Callable 时我们知道它可以有返回值,返回值通过 Future 进行封装。FutureTask 实现了 RunnableFuture 接口,该接口继承自 Runnable 和 Future 接口,这使得 FutureTask 既可以当做一个任务执行,也可以有返回值。

FutureTask 可用于异步获取执行结果或取消执行任务的场景。当一个计算任务需要执行很长时间,那么就可以用 FutureTask 来封装这个任务,主线程在完成自己的任务之后再去获取结果。

BlockingQueue:

  • FIFO 队列 :LinkedBlockingQueue、ArrayBlockingQueue(固定长度)
  • 优先级队列 :PriorityBlockingQueue

提供了阻塞的 take() 和 put() 方法:如果队列为空 take() 将阻塞,直到队列中有内容;如果队列为满 put() 将阻塞,直到队列有空闲位置。

ForkJoin:

主要用于并行计算中,和 MapReduce 原理类似,都是把大的计算任务拆分成多个小任务并行计算。ForkJoin 使用 ForkJoinPool 来启动,它是一个特殊的线程池,线程数量取决于 CPU 核数。ForkJoinPool 实现了工作窃取算法来提高 CPU 的利用率。每个线程都维护了一个双端队列,用来存储需要执行的任务。工作窃取算法允许空闲的线程从其它线程的双端队列中窃取一个任务来执行。窃取的任务必须是最晚的任务,避免和队列所属线程发生竞争。

猜你喜欢

转载自www.cnblogs.com/liushoudong/p/12725992.html