J.U.C之AQS

一、AbstractQueuedSynchronizer-AQS(J.U.C核心)

    提供了一个FIFO队列SyncQueue,可用于构建锁,或其他相关同步机制的基础框架,底层使用双向链表实现队列。还提供了一个ConditionQueue(单向链表),只有程序中用到Condition时,才会涉及到ConditionQueue。
    AQS内部维护了一个队列管理锁,线程尝试获取锁,如果失败,就把当前线程和等待信息包装成一个Node节点,放入到同步队列Sync queue中,不断循环尝试获取锁。

1.Sync queue

    1)使用Node实现FIFO队列,可以用于构建锁或者其他同步装置的基础框架。

    2)利用了一个int类型表示状态,AQS类中维护了一个state成员变量,在ReentrantLock中 state=0表示还没有线程获取锁, state=1表示有线程获取锁, state>1表示可重入锁的数量。
    3)使用方法是继承,使用者需要复写其中的方法,子类通过继承并实现其中的方法管理其状态(如acquire()和release())。
    4)可以实现排它锁和共享锁模式(独占控制、共享控制两种功能)。
2.AQS同步组件
    1) CountDownLatch
    CountDownLatch是一个同步辅助类,利用它可以实现类似于阻塞当前线程的功能,一个或多个线程一直等待,直到其他线程处理完成,利用了一个给定的计数器进行初始化,该计数器的操作是原子性的操作,当其他线程执行完调用countDown()将计数器的值减一,当计数器的值为0时所有因掉用await()方法而处于等待状态的线程就会被激化继续向下执行,计数器不可被重置。
    使用场景:在某些业务中程序需要等待一些条件执行完成后再继续执行后续操作,如并行计算,当所有子计算任务完成后,才可以将所有子任务的结果进行最终计算。

    2)Semaphore
   Semaphore可以控制并发访问的线程的个数,提供了 acquire()和release()方法进行许可的获取和释放,利用tryAcquire()方法判断是否得到许可,如果获取进行后续执行(可用于抛弃其他线程的场景)。
    使用场景:用于仅能提供有限访问的资源的场景,如数据库的访问量是有限的,而上层请求远远大于数据库的最大访问量,这时就可以使用Semphore进行数量上的控制。也可以用于只需要有限个数的线程,剩余线程可以抛弃。
    3)CylicBarrier
   CylicBarrier也是一个同步辅助类,允许一组线程相互等待,等待到达某个公共的屏障点,也就是每个线程都准备就绪后,才能各自继续向下执行后续操作,内部维护了一个计数器,当一个线程调用await方法进入等待状态时计数器加一,当计数器的值等于我们设置的值如:3时,调用await()方法进入等待状态的线程会被唤醒进行后续操作。
    使用场景:用于多线程计算数据,先计算各个线程的统计结果,随后统计各个线程的计算结果。

    
    4)reentrantLock
    ReentrantLock和Synchronized区别
    a.可重入性,同一个线程加一次锁所得计数器加1,只有当计数器为0时才可以释放锁。
    b.锁的实现,Synchronized是依赖与JVM实现,ReentrantLock是JDK实现。
    c.性能的区别,Synchronized引入自旋锁后性能与ReentrantLock其实差不多,更推荐使用Synchronized,代码更便捷。
    d.功能区别,Synchronized的使用更便捷,ReentrantLock需要手动加锁和释放锁,但是ReentrantLock的细粒度优于Synchronized。
    e.ReentrantLock独有的功能
        ·可指定是公平锁(先等待的线程先获取锁)还是非公平锁,Synchronized只是非公平锁。
        ·提供了Condition类,可以分组唤醒需要唤醒的线程。Synchromized要么唤醒一个线程,要么唤醒所有线程。
         ·提供了能够中断等待锁的线程的机制,lock.localInterruptibly()。
    ReentrantLock的实现是使用自旋锁,循环调用CAS操作来加锁,当业务场景需要使用到上述三个独有的功能时,就需要使用ReentrantLock,否则可以根据业务场景决定使用ReentrantLock还是Synchronized。

    5)ReentrantReadWriteLock
    ReenTranReadWriteLock内部通过两个内部类ReadLock和WriteLock实现Lock接口,分别对读操作和写操作加锁。
    6)StampedLock
    StampedLock有三种锁,写、读、乐观读(读操作很多,写操作很少,认为写于读同时发生的几率很小)。
    5)Condition
    多线程间的协调工具类,Condition的执行方式,是当在线程1中调用await()方法后,线程1将释放锁,并且将自己沉睡,等待唤醒,线程2获取到锁时,开始做事,完毕后,调用Condition的signal方法,唤醒线程1,线程1恢复执行。
以上说明Condition是一个多线程间协调通信的工具类,使得某个,或者某些线程一起等待某个条件(Condition),只有当该条件具备(signal或signalAll方法被调用)时,这些线程才会被唤醒,从而重新争夺锁。
    6)Callable和FutureTask
   使用多线程时,一般我们是通过继承Thread类或者重写Runnable()方法,但是这两种方法都无法获得线程处理后的结果,所以在JDK1.5后就提供了Callable和FutureTask
    Callable与Runnable接口对比,Callable可以在线程执行后有返回值。
   Future接口, Future<V>接口是用来获取异步计算结果的,说白了就是对具体的Runnable或者Callable对象任务执行的结果进行获取(get()),取消(cancel()),判断是否完成等操作
     FutureTask类,实现了CallableFuture接口, FutureTask除了实现了Future接口外还实现了Runnable接口,因此FutureTask也可以直接提交给Executor执行。 当然也可以调用线程直接执行(FutureTask.run())。
    7)ForkJoin
     Fork/Join框架是Java7提供了的一个用于并行执行任务的框架, 是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架。我们再通过Fork和Join这两个单词来理解下Fork/Join框架,Fork就是把一个大任务切分为若干子任务并行的执行,Join就是合并这些子任务的执行结果,最后得到这个大任务的结果。比如计算1+2+。。+10000,可以分割成10个子任务,每个子任务分别对1000个数进行求和,最终汇总这10个子任务的结果。
    Fork/Join主要应用了工作窃取算法, 工作窃取(work-stealing)算法是指某个线程从其他队列里窃取任务来执行,那么为什么需要使用工作窃取算法呢?假如我们需要做一个比较大的任务,我们可以把这个任务分割为若干互不依赖的子任务,为了减少线程间的竞争,于是把这些子任务分别放到不同的队列里,并为每个队列创建一个单独的线程来执行队列里的任务,线程和队列一一对应,比如A线程负责处理A队列里的任务。但是有的线程会先把自己队列里的任务干完,而其他线程对应的队列里还有任务等待处理。干完活的线程与其等着,不如去帮其他线程干活,于是它就去其他线程的队列里窃取一个任务来执行。而在这时它们会访问同一个队列,所以为了减少窃取任务线程和被窃取任务线程之间的竞争,通常会使用双端队列,被窃取任务线程永远从双端队列的头部拿任务执行,而窃取任务的线程永远从双端队列的尾部拿任务执行。
    工作窃取算法的优点是充分利用线程进行并行计算,并减少了线程间的竞争,其缺点是在某些情况下还是存在竞争,比如双端队列里只有一个任务时。并且消耗了更多的系统资源,比如创建多个线程和多个双端队列。


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值