java并发编程笔记(六)——AQS

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

利用了一个int类型表示状态

使用方法是继承

子类通过继承并通过实现它的方法管理其状态(acquire和release)的方法操纵状态

可以同时实现排它锁和共享锁模式(独占、共享)

AQS同步组件

  • CountDownLatch
  • Semaphore
  • CyclicBarrier
  • ReentrantLock
  • Condition
  • FutureTask

CountDownLatch

  1. public class CountDownLatchExample1 {
  2. private final static int threadCount = 200;
  3. public static void main(String[] args) throws Exception {
  4. ExecutorService exec = Executors.newCachedThreadPool();
  5. final CountDownLatch countDownLatch = new CountDownLatch(threadCount);
  6. for (int i = 0; i < threadCount; i++) {
  7. final int threadNum = i;
  8. exec.execute(() -> {
  9. try {
  10. test(threadNum);
  11. } catch (Exception e) {
  12. log.error("exception", e);
  13. } finally {
  14. countDownLatch.countDown();
  15. }
  16. });
  17. }
  18. countDownLatch.await();
  19. #countDownLatch.await(10, TimeUnit.MILLISECONDS); //这种方式可以设置超时时间,如果指定时间未完成,就结束等待
  20. log.info("finish");
  21. exec.shutdown();
  22. }
  23. private static void test(int threadNum) throws Exception {
  24. Thread.sleep(100);
  25. log.info("{}", threadNum);
  26. Thread.sleep(100);
  27. }
  28. }

Semaphore

信号量,控制某个资源同时被访问的个数

1、适用于仅能提供有限资源访问的场景,如:数据库连接

  1. //基本使用
  2. public class SemaphoreExample1 {
  3. private final static int threadCount = 20;
  4. public static void main(String[] args) throws Exception {
  5. ExecutorService exec = Executors.newCachedThreadPool();
  6. final Semaphore semaphore = new Semaphore(3);
  7. for (int i = 0; i < threadCount; i++) {
  8. final int threadNum = i;
  9. exec.execute(() -> {
  10. try {
  11. semaphore.acquire(); // 获取一个许可
  12. //semaphore.acquire(3); // 获取多个许可
  13. test(threadNum);
  14. semaphore.release(); // 释放一个许可
  15. //semaphore.release(); // 释放多个许可
  16. } catch (Exception e) {
  17. log.error("exception", e);
  18. }
  19. });
  20. }
  21. exec.shutdown();
  22. }
  23. private static void test(int threadNum) throws Exception {
  24. log.info("{}", threadNum);
  25. Thread.sleep(1000);
  26. }
  27. }
  1. //尝试获取许可
  2. public class SemaphoreExample3 {
  3. private final static int threadCount = 20;
  4. public static void main(String[] args) throws Exception {
  5. ExecutorService exec = Executors.newCachedThreadPool();
  6. final Semaphore semaphore = new Semaphore(3);
  7. for (int i = 0; i < threadCount; i++) {
  8. final int threadNum = i;
  9. exec.execute(() -> {
  10. try {
  11. if (semaphore.tryAcquire()) { // 尝试获取一个许可
  12. test(threadNum);
  13. semaphore.release(); // 释放一个许可
  14. }
  15. } catch (Exception e) {
  16. log.error("exception", e);
  17. }
  18. });
  19. }
  20. exec.shutdown();
  21. }
  22. private static void test(int threadNum) throws Exception {
  23. log.info("{}", threadNum);
  24. Thread.sleep(1000);
  25. }
  26. }
  1. //在等待的时间内,尝试获取许可
  2. public class SemaphoreExample4 {
  3. private final static int threadCount = 20;
  4. public static void main(String[] args) throws Exception {
  5. ExecutorService exec = Executors.newCachedThreadPool();
  6. final Semaphore semaphore = new Semaphore(3);
  7. for (int i = 0; i < threadCount; i++) {
  8. final int threadNum = i;
  9. exec.execute(() -> {
  10. try {
  11. if (semaphore.tryAcquire(5000, TimeUnit.MILLISECONDS)) { // 尝试获取一个许可
  12. test(threadNum);
  13. semaphore.release(); // 释放一个许可
  14. }
  15. } catch (Exception e) {
  16. log.error("exception", e);
  17. }
  18. });
  19. }
  20. exec.shutdown();
  21. }
  22. private static void test(int threadNum) throws Exception {
  23. log.info("{}", threadNum);
  24. Thread.sleep(1000);
  25. }
  26. }

CyclicBarrier

允许一组线程相互等待,直到到达某个公共的屏障点。

可以完成多个线程相互等待,只有当每个线程都准备就绪后,才能各自继续往下执行后面的操作。

与CountDownLatch很相似,但存在几个差异点:

1、CountDownLatch是一次性的,用完就销毁了,CyclicBarrier可以通过reset()方法重置,重复使用。

2、CountDownLatch主要是实现一个或N个线程需要等待其他线程完成某项操作之后,才能继续往下执行;而CyclicBarrier主要是实现了多个线程之间相互等待,直到所有的线程都满足了条件之后才能继续后续的操作,它描述的是各个线程内部相互等待的关系。比如我们设置了初始值是5,只有当5个线程都达到某个条件了,才能继续往下执行。

  1. //基本使用
  2. public class CyclicBarrierExample1 {
  3. private static CyclicBarrier barrier = new CyclicBarrier(5);
  4. public static void main(String[] args) throws Exception {
  5. ExecutorService executor = Executors.newCachedThreadPool();
  6. for (int i = 0; i < 10; i++) {
  7. final int threadNum = i;
  8. Thread.sleep(1000);
  9. executor.execute(() -> {
  10. try {
  11. race(threadNum);
  12. } catch (Exception e) {
  13. log.error("exception", e);
  14. }
  15. });
  16. }
  17. executor.shutdown();
  18. }
  19. private static void race(int threadNum) throws Exception {
  20. Thread.sleep(1000);
  21. log.info("{} is ready", threadNum);
  22. barrier.await();
  23. log.info("{} continue", threadNum);
  24. }
  25. }
  1. public class CyclicBarrierExample2 {
  2. private static CyclicBarrier barrier = new CyclicBarrier(5);
  3. public static void main(String[] args) throws Exception {
  4. ExecutorService executor = Executors.newCachedThreadPool();
  5. for (int i = 0; i < 10; i++) {
  6. final int threadNum = i;
  7. Thread.sleep(1000);
  8. executor.execute(() -> {
  9. try {
  10. race(threadNum);
  11. } catch (Exception e) {
  12. log.error("exception", e);
  13. }
  14. });
  15. }
  16. executor.shutdown();
  17. }
  18. private static void race(int threadNum) throws Exception {
  19. Thread.sleep(1000);
  20. log.info("{} is ready", threadNum);
  21. try {
  22. barrier.await(2000, TimeUnit.MILLISECONDS); //设置等待超时时间
  23. } catch (Exception e) {
  24. log.warn("BarrierException", e);
  25. }
  26. log.info("{} continue", threadNum);
  27. }
  28. }
  1. public class CyclicBarrierExample3 {
  2. private static CyclicBarrier barrier = new CyclicBarrier(5, () -> {
  3. log.info("callback is running");
  4. }); /线程达到屏障点的的时候,优先执行这个方法
  5. public static void main(String[] args) throws Exception {
  6. ExecutorService executor = Executors.newCachedThreadPool();
  7. for (int i = 0; i < 10; i++) {
  8. final int threadNum = i;
  9. Thread.sleep(1000);
  10. executor.execute(() -> {
  11. try {
  12. race(threadNum);
  13. } catch (Exception e) {
  14. log.error("exception", e);
  15. }
  16. });
  17. }
  18. executor.shutdown();
  19. }
  20. private static void race(int threadNum) throws Exception {
  21. Thread.sleep(1000);
  22. log.info("{} is ready", threadNum);
  23. barrier.await();
  24. log.info("{} continue", threadNum);
  25. }
  26. }

J.U.C相关的锁

ReentrantLock与锁

锁的种类:

  • synchronized
  • J.U.C中提供的锁,核心锁就是ReentrantLock

ReentrantLock(可重入锁)和synchronized区别

  • 可重入性:前者是可重入锁,后者也是可重入锁,两者相差不大

  • 锁的实现:后者是依赖JVM实现的,前者是JDK实现的

  • 性能的区别:自从后者引入了轻量锁,偏向锁,自选锁后其性能与前者相差不大,由于后者使用方便,可读性高,建议使用后者。

  • 功能的区别:

    • 后者比前者便利,后者是通过编译器去控制加锁和释放锁的,而后者需要调用者手动去加锁和释放锁。
    • 前者锁的细粒度比后者好。
  • ReentrantLock独有的功能(当需要实现下述这几种独有的功能时,必须使用ReentrantLock)

    • 可以指定是公平锁还是非公平锁(sychronized只能是非公平锁);

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

      1. public class LockExample6 {
      2. public static void main(String[] args) {
      3. ReentrantLock reentrantLock = new ReentrantLock();
      4. Condition condition = reentrantLock.newCondition();
      5. new Thread(() -> {
      6. try {
      7. reentrantLock.lock(); //这个时候线程就加入到了AQS的等待队列里去
      8. log.info("wait signal"); // 1
      9. condition.await(); //线程从AQS的等待队列里移除了,对应的操作其实是锁的释放,接着就加入到了condition的等待队列里去
      10. } catch (InterruptedException e) {
      11. e.printStackTrace();
      12. }
      13. log.info("get signal"); // 4
      14. reentrantLock.unlock();
      15. }).start();
      16. new Thread(() -> {
      17. reentrantLock.lock(); //因为线程1释放的锁的缘故,所以这里可以取到锁
      18. log.info("get lock"); // 2
      19. try {
      20. Thread.sleep(3000);
      21. } catch (InterruptedException e) {
      22. e.printStackTrace();
      23. }
      24. condition.signalAll(); //发送信号,这个时候condition等待队列里有线程1的节点,执行完之后线程1从condition等待队列里取出来了,加入到了AQS的等待队列了
      25. log.info("send signal ~ "); // 3
      26. reentrantLock.unlock(); //线程2释放锁,因此线程1被唤醒
      27. }).start();
      28. }
      29. }
    • 提供能够中断等待锁的线程的机制,lock.lockInterruptibly()

    1. public class LockExample2 {
    2. // 请求总数
    3. public static int clientTotal = 5000;
    4. // 同时并发执行的线程数
    5. public static int threadTotal = 200;
    6. public static int count = 0;
    7. private final static Lock lock = new ReentrantLock();
    8. public static void main(String[] args) throws Exception {
    9. ExecutorService executorService = Executors.newCachedThreadPool();
    10. final Semaphore semaphore = new Semaphore(threadTotal);
    11. final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
    12. for (int i = 0; i < clientTotal ; i++) {
    13. executorService.execute(() -> {
    14. try {
    15. semaphore.acquire();
    16. add();
    17. semaphore.release();
    18. } catch (Exception e) {
    19. log.error("exception", e);
    20. }
    21. countDownLatch.countDown();
    22. });
    23. }
    24. countDownLatch.await();
    25. executorService.shutdown();
    26. log.info("count:{}", count);
    27. }
    28. private static void add() {
    29. lock.lock();
    30. try {
    31. count++;
    32. } finally {
    33. lock.unlock();
    34. }
    35. }
    36. }

ReentranReadWriteLock锁

在没有任何读写锁的情况下,才可以取得写入的锁

  1. @Slf4j
  2. public class LockExample3 {
  3. private final Map<String, Data> map = new TreeMap<>();
  4. private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
  5. private final Lock readLock = lock.readLock();
  6. private final Lock writeLock = lock.writeLock();
  7. public Data get(String key) {
  8. readLock.lock();
  9. try {
  10. return map.get(key);
  11. } finally {
  12. readLock.unlock();
  13. }
  14. }
  15. public Set<String> getAllKeys() {
  16. readLock.lock();
  17. try {
  18. return map.keySet();
  19. } finally {
  20. readLock.unlock();
  21. }
  22. }
  23. public Data put(String key, Data value) {
  24. writeLock.lock();
  25. try {
  26. return map.put(key, value);
  27. } finally {
  28. writeLock.unlock();
  29. }
  30. }
  31. class Data {
  32. }
  33. }

StampedLock锁

控制锁有三种模式:

  • 乐观读

如果读的操作很多,写的操作很少的情况下,我们可以乐观的认为写入和读取同时发生的概率很小,因此不悲观使用完全锁定;因此在性能上有很大的提升。

案例:

  1. public class LockExample5 {
  2. // 请求总数
  3. public static int clientTotal = 5000;
  4. // 同时并发执行的线程数
  5. public static int threadTotal = 200;
  6. public static int count = 0;
  7. private final static StampedLock lock = new StampedLock();
  8. public static void main(String[] args) throws Exception {
  9. ExecutorService executorService = Executors.newCachedThreadPool();
  10. final Semaphore semaphore = new Semaphore(threadTotal);
  11. final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
  12. for (int i = 0; i < clientTotal ; i++) {
  13. executorService.execute(() -> {
  14. try {
  15. semaphore.acquire();
  16. add();
  17. semaphore.release();
  18. } catch (Exception e) {
  19. log.error("exception", e);
  20. }
  21. countDownLatch.countDown();
  22. });
  23. }
  24. countDownLatch.await();
  25. executorService.shutdown();
  26. log.info("count:{}", count);
  27. }
  28. private static void add() {
  29. long stamp = lock.writeLock();
  30. try {
  31. count++;
  32. } finally {
  33. lock.unlock(stamp);
  34. }
  35. }
  36. }

锁总结

  • 当只有少量的竞争者的时候,sychrnozied是一个很好地锁实现
  • 竞争者不少但是线程增长的趋势我们能够预估,这个时候ReetrantLock是一个很好的锁实现

FutureTask

Callable和Runnable接口的对比

callable:

在线程执行完之后,可以获取线程执行的结果;

有一个call()方法,有返回值;

Runnable:

只有run()方法

Future接口

查询任务的执行情况,可以监视线程的执行情况,如果调用Future.get()方法,加入任务还没有执行完,那么当前线程就会阻塞,直到获取到结果。

  1. @Slf4j
  2. public class FutureExample {
  3. static class MyCallable implements Callable<String> {
  4. @Override
  5. public String call() throws Exception {
  6. log.info("do something in callable");
  7. Thread.sleep(5000);
  8. return "Done";
  9. }
  10. }
  11. public static void main(String[] args) throws Exception {
  12. ExecutorService executorService = Executors.newCachedThreadPool();
  13. Future<String> future = executorService.submit(new MyCallable());
  14. log.info("do something in main");
  15. Thread.sleep(1000);
  16. String result = future.get();
  17. log.info("result:{}", result);
  18. }
  19. }

FutureTask类

实现了两个接口,Runnable和Future,使用起来会很方便。

  1. @Slf4j
  2. public class FutureTaskExample {
  3. public static void main(String[] args) throws Exception {
  4. FutureTask<String> futureTask = new FutureTask<String>(new Callable<String>() {
  5. @Override
  6. public String call() throws Exception {
  7. log.info("do something in callable");
  8. Thread.sleep(5000);
  9. return "Done";
  10. }
  11. });
  12. new Thread(futureTask).start();
  13. log.info("do something in main");
  14. Thread.sleep(1000);
  15. String result = futureTask.get();
  16. log.info("result:{}", result);
  17. }
  18. }

Fork/Join框架

是java7提供的并行执行任务的一个框架,把大任务分割成若干个小任务;

参考案例:

  1. @Slf4j
  2. public class ForkJoinTaskExample extends RecursiveTask<Integer> {
  3. public static final int threshold = 2;
  4. private int start;
  5. private int end;
  6. public ForkJoinTaskExample(int start, int end) {
  7. this.start = start;
  8. this.end = end;
  9. }
  10. @Override
  11. protected Integer compute() {
  12. int sum = 0;
  13. //如果任务足够小就计算任务
  14. boolean canCompute = (end - start) <= threshold;
  15. if (canCompute) {
  16. for (int i = start; i <= end; i++) {
  17. sum += i;
  18. }
  19. } else {
  20. // 如果任务大于阈值,就分裂成两个子任务计算
  21. int middle = (start + end) / 2;
  22. ForkJoinTaskExample leftTask = new ForkJoinTaskExample(start, middle);
  23. ForkJoinTaskExample rightTask = new ForkJoinTaskExample(middle + 1, end);
  24. // 执行子任务
  25. leftTask.fork();
  26. rightTask.fork();
  27. // 等待任务执行结束合并其结果
  28. int leftResult = leftTask.join();
  29. int rightResult = rightTask.join();
  30. // 合并子任务
  31. sum = leftResult + rightResult;
  32. }
  33. return sum;
  34. }
  35. public static void main(String[] args) {
  36. ForkJoinPool forkjoinPool = new ForkJoinPool();
  37. //生成一个计算任务,计算1+2+3+4
  38. ForkJoinTaskExample task = new ForkJoinTaskExample(1, 100);
  39. //执行一个任务
  40. Future<Integer> result = forkjoinPool.submit(task);
  41. try {
  42. log.info("result:{}", result.get());
  43. } catch (Exception e) {
  44. log.error("exception", e);
  45. }
  46. }
  47. }

BlockingQueue

主要是用于生产者消费者的情景,这个组件主要是保证线程安全的,在队列重视按照顺序先进先执行的顺序进行执行的。

  • ArrayBlockingQueue

    有界的阻塞队列,内部实现是一个数组,必须要在初始化的时候指定大小。

  • DelayQueue

    阻塞的是内部元素,内部元素必须实现一个接口delayed

  • LinkedBlockingQueue

    可扩展大小的阻塞队列

  • PriorityBlockingQueue

    有优先级的阻塞队列

  • SychronousQueue

    仅容许一个元素

java并发编程笔记(六)——AQS的更多相关文章

  1. 【Java并发编程实战】----- AQS(三):阻塞、唤醒:LockSupport

    在上篇博客([Java并发编程实战]----- AQS(二):获取锁.释放锁)中提到,当一个线程加入到CLH队列中时,如果不是头节点是需要判断该节点是否需要挂起:在释放锁后,需要唤醒该线程的继任节点 ...

  2. java并发编程笔记(十一)——高并发处理思路和手段

    java并发编程笔记(十一)--高并发处理思路和手段 扩容 垂直扩容(纵向扩展):提高系统部件能力 水平扩容(横向扩容):增加更多系统成员来实现 缓存 缓存特征 命中率:命中数/(命中数+没有命中数) ...

  3. java并发编程笔记(十)——HashMap与ConcurrentHashMap

    java并发编程笔记(十)--HashMap与ConcurrentHashMap HashMap参数 有两个参数影响他的性能 初始容量(默认为16) 加载因子(默认是0.75) HashMap寻址方式 ...

  4. java并发编程笔记(九)——多线程并发最佳实践

    java并发编程笔记(九)--多线程并发最佳实践 使用本地变量 使用不可变类 最小化锁的作用域范围 使用线程池Executor,而不是直接new Thread执行 宁可使用同步也不要使用线程的wait ...

  5. java并发编程笔记(八)——死锁

    java并发编程笔记(八)--死锁 死锁发生的必要条件 互斥条件 进程对分配到的资源进行排他性的使用,即在一段时间内只能由一个进程使用,如果有其他进程在请求,只能等待. 请求和保持条件 进程已经保持了 ...

  6. java并发编程笔记(七)——线程池

    java并发编程笔记(七)--线程池 new Thread弊端 每次new Thread新建对象,性能差 线程缺乏统一管理,可能无限制的新建线程,相互竞争,有可能占用过多系统资源导致死机或者OOM 缺 ...

  7. java并发编程笔记(五)——线程安全策略

    java并发编程笔记(五)--线程安全策略 不可变得对象 不可变对象需要满足的条件 对象创建以后其状态就不能修改 对象所有的域都是final类型 对象是正确创建的(在对象创建期间,this引用没有逸出 ...

  8. java并发编程笔记(四)——安全发布对象

    java并发编程笔记(四)--安全发布对象 发布对象 使一个对象能够被当前范围之外的代码所使用 对象逸出 一种错误的发布.当一个对象还没构造完成时,就使它被其他线程所见 不安全的发布对象 某一个类的构 ...

  9. java并发编程笔记(三)——线程安全性

    java并发编程笔记(三)--线程安全性 线程安全性: ​ 当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些进程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现 ...

随机推荐

  1. python 按二维数组的某行或列排序 (numpy lexsort)

    lexsort支持对数组按指定行或列的顺序排序:是间接排序,lexsort不修改原数组,返回索引. (对应lexsort 一维数组的是argsort a.argsort()这么使用就可以:argsor ...

  2. VS2017使用assimp 5.0.0 error C2589: '(' : illegal token on right side of '::' 解决办法

    坑爹微软Sucks Again. assimp 终于更新到了5.0.0并且支持GLTF2格式,包含动画正确解析,在viewer中也能看到正确结果,真他喵的不容易,然后拿来编译完到自己项目里用,就出这玩 ...

  3. [LeetCode] 477. Total Hamming Distance(位操作)

    传送门 Description The Hamming distance between two integers is the number of positions at which the co ...

  4. RMQ(连续相同最大值)

    http://poj.org/problem?id=3368 Frequent values Time Limit: 2000MS   Memory Limit: 65536K Total Submi ...

  5. python学习第二十八天函数局部变量的用法

    函数局部变量是在函数里面的变量,只能在函数内部使用,如果函数没有找对应变量,函数将去函数外部找对应变量,局部变量优先级大于外部变量,详细说明一下 1,局部变量已经定义值 name='zhan san' ...

  6. python学习第十五天字典的创建及增删改查操作方法

    字典是python比较常见的数据类型,跟列表一样,比如的字典的创建,字典的常见的操作的方法,增加,删除,修改,查找等方法,字典的一共的数据方法为 keys()  values() fromkeys() ...

  7. What is one-hot?

    SEO: libtorch 如何 OneHot ? torch OneHot 源代码 ? https://www.tensorflow.org/api_docs/python/tf/one_hot 最 ...

  8. Android关于SurfaceView,SurfaceHolder,SurfaceHolder.CallBack详解

    官方的定义: 1.SurfaceView SurfaceView是视图(View)的继承类,这个视图里内嵌了一个专门用于绘制的Surface.你可以控制这个Surface的格式和尺寸.Surfacev ...

  9. JVM中类加载器的父委托机制

    类加载器 类加载器用来把类加载到Java虚拟机中. 类加载器的类型 有两种类型的类加载器: 1.JVM自带的加载器: 根类加载器(Bootstrap) 扩展类加载器(Extension) 系统类加载器 ...

  10. Git相关命令整理

    git config --global user.name  //配置姓名git config --global user.email  //配置邮箱git config --list  //查看配置 ...