慕课网高并发实战(七)- J.U.C之AQS

7.1  AbstractQueuedSynchronizer -AQS

底层实现了双向链表,是队列的一种实现方式

对象创建以后其状态就不能修改




底层是双向链表,队列的一种实现

sync queue:同步队列,head节点主要负责后面的调度

Condition queue:单向链表,不是必须的的,也可以有多个

设计原理

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

利用了一个int类型标示状态,有一个state的成员变量,表示获取锁的线程数(0没有线程获取锁,1有线程获取锁,大于1表示重入锁的数量),和一个同步组件ReentrantLock,

使用方法是继承,基于模板方法

子类通过继承并通过实现它的方法管理其状态{acquire和release}的方法操作状态

可以实现排它锁和共享锁的模式(独占、共享,子类只能实现其中一个)

具体实现的思路

1.首先 AQS内部维护了一个CLH队列,来管理锁

线程尝试获取锁,如果获取失败,则将等待信息等包装成一个Node结点,加入到同步队列Sync queue里

3.不断重新尝试获取锁(当前结点为head的直接后继才会 尝试),如果获取失败,则会阻塞自己,直到被唤醒

4.当持有锁的线程释放锁的时候,会唤醒队列中的后继线程

AQS同步组件

CountDownLatch(闭锁,通过一个计数保证线程是否需要一直阻塞 )

Semaphore(控制同一时间并发线程的数目)

CyclicBarrier(与CountDownLatch 相识 阻塞线程,可以重置计数器)

ReentrantLock

Condition

FutureTask

CountDownLatch

同步阻塞类,可以完成阻塞线程的功能








*&&&& CountDownLatch :闭锁,通过一个计数,判断线程是否阻塞

&&&&  Semaphore:控制并发线程的数目


7.2  CountDownLatch 

同步辅组类,完成阻塞当前线程的功能,给定了一个计数器,原子操作,计数器不能重置。

调用await()方法会使线程处于阻塞状态,直到其他线程调用CountDown()方法时,才继续执行

当计数器变为0的时候,所有等待的线程才会继续执行

使用场景:查询需要等待某个条件完成后才能继续执行后续操作(Ex:并行计算)拆分任务



7.3 Semaphore


并发访问控制线程个数(同步机制),   提供了两个方法,实现有限大小的链表大小

semaphore.acquire(); // 获取一个许可

semaphore.release(); // 释放一个许可

semaphore.acquire(n);//获取多个许可

semaphore.release(n); // 释放n个许可


使用场景:仅能提供有限访问的资源,比如数据库连接数


tryAcquire())//尝试获取一个许可


tryAcquire 四个带参方法

 1 tryAcquire(long timeout, TimeUnit unit)

 2  tryAcquire()

3  tryAcquire(int permits)

4  boolean tryAcquire(int permits, long timeout, TimeUnit unit)


7.4  CyclicBarrier



运行一组线程等待到一个公共的屏障点,实现多个线程相互等待,当每一个线程都就绪后,才执行下去,通过计数器实现的

多线程计算数据,最后合并的场景


CyclicBarrier 与CountDownLatch 的区别

1  CountDownLatch 的计数器只能使用一次,CyclicBarrier 可以使用reset()方法重置

2 CountDownLatch 实现一个或者n个线程需要等待其他线程执行某项操作后才能继续执行 

 CyclicBarrier  实现多个线程了多个线程相互等待,知道多个线程都满足了某个条件以后才继续执行

描述的多个线程内部的关系,多个线程都调用await()方法后才继续向下执行

提供方法获取阻塞线程的个数,知道阻塞的线程是否中断

CyclicBarrier  对象调用await() 等待多个线程都满足条件后,在往下面执行


//定义有5个线程同步等待

1) private static CyclicBarrierbarrier =new CyclicBarrier(5);

2 )

在5个线程都满足条件后,先执行 log.info("callback is running"); 在执行以后的代码

private static CyclicBarrierbarrier =new CyclicBarrier(5, () -> {

log.info("callback is running");

});



7.5 ReentrantLock 与锁


java  两类锁: 1 synchronized 

                       2 JUC的 ReentrantLock 


 ReentrantLock 与synchronized 的区别

1 &&&  可重入性 两者都是可重入锁 ,同一线程进入一次 锁的计数器就自增1 ,锁的计数器下降为0 时才释放锁

2 &&&  synchronized 是依赖jvm实现的(操作系统实现,难查源码),ReentrantLock 是依赖jdk实现的(用户实现)

3 &&&  两者性能差不多 ,推荐使用synchronized ,synchronized 优化借鉴了CAS技术,用户态解决加锁问题避免进入内核态 使线程阻塞

4 &&&  synchronized  更方便它是编译器保证锁的加锁和释放的,ReentrantLock 手工释放和加锁,在finally释放锁

锁的细腻度和灵活度 ReentrantLock 更好 

ReentrantLock  的独有的功能

1  ReentrantLock 可指定是公平锁和非公平锁  synchronized 只能是非公平锁

公平锁(先等待的线程先获得锁)

2 提供了一个Condition类,可以分组唤醒需要唤醒的线程

synchronized  唤醒一个要不全部唤醒

3  提供能够中断等待锁的线程的机制,lock.lockInterruptibly()

ReentrantLock 实现是一种自旋锁,通过循环调用cas操作自加操作,避免了线程进入内核态发生阻塞 

synchronized 不会忘记释放锁

ReentrantLock 函数方法

tryLock():仅在调用时锁定未被另外一个线程保持的情况下获取锁定

tryLock(long timeout, TimeUnit unit) 如果锁定在给定的时间内没有被另一个线程保持,且当前线程没有被中断,则获取这个锁定

lockInterruptibly() 当前线程如果没有中断就获取锁定,如果已经中断就抛出异常

isLocked() 查询当前此锁定是否由任意线程保持,


ReentrantReadWriteLock   

 在没有任何 读写锁(ReadWrite)*的情况下才能取得写锁(Write)


StampedLock 

版本和模式两个部分组成

控制锁的三种方式:

1 写

2 读

3 乐观读

锁获取的方法是一个数字,用锁的状态控制相关锁的状态的访问

数字0 表示没有写锁

读锁分为 悲观锁 和乐观锁

对吞吐量有巨大的改进,特别是读线程多的场景中下

StampedLock  对于加锁容易误用其他的方法


Condition 


package com.mmall.concurrency.example.lock;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.Condition;

import java.util.concurrent.locks.ReentrantLock;

@Slf4j

public class LockExample6 {

public static void main(String[] args) {

ReentrantLock reentrantLock =new ReentrantLock();

        Condition condition = reentrantLock.newCondition();

        new Thread(() -> {

try {

reentrantLock.lock(); // 线程加入到AQS的等待队列中

                log.info("wait signal"); // 1

                condition.await(); //调用await()方法后 线程从AQS对列中溢出(锁的释放),进入到condition的等待队列中

            }catch (InterruptedException e) {

e.printStackTrace();

            }

log.info("get signal"); // 4

            reentrantLock.unlock();

        }).start();

        new Thread(() -> {

reentrantLock.lock();

            log.info("get lock"); // 2

            try {

Thread.sleep(3000);

            }catch (InterruptedException e) {

e.printStackTrace();

            }

condition.signalAll();

            log.info("send signal ~ "); // 3

            reentrantLock.unlock();

        }).start();

    }

}

result:

- wait signal

- get lock

- send signal ~

- get signal




总结

1 只有少量竞争者的时候,synchronized是比较好的选择 

2 竞争者不少,线程的数量可以预估的,ReentrantLock 是一个比较好的锁实现

推荐阅读更多精彩内容

  • Java并发总结 1.多线程的优点 资源利用率更好 程序在某些情况下更简单 程序响应更快 2.创建线程 1.实现R...
    不会上树的猴子阅读 573评论 0 5
  • 1.明明就已经一大把年纪,却还是渴望童话世界,把自己当成公主,期待白马王子的出现。 2.明明才二十多岁,就开始每天...
    爱吃肉肉的小蜻蜓阅读 58评论 0 0
  • 关于AndroidAsync AndroidAsync封装了常用的异步请求比如获取字符串、获取JSON、获取文件等...
    黄海佳阅读 4,232评论 0 1
  • 2017高考作文诗歌 我拿着单反,骑着共享单车 今天是高考四十年,我的 高考在汽车火车自行车 的一带一路上预测 那...
    冰眉铁面阅读 215评论 0 3