10分钟搞定Java并发队列

前言

如果按照用途与特性进行粗略的划分,JUC 包中包含的工具大体可以分为 6 类:

  1. 执行者与线程池
  2. 并发队列
  3. 同步工具
  4. 并发集合
  5. 原子变量

在并发系列中,主要讲解了 执行者与线程池,同步工具,锁 , 在分析源码时,或多或少的提及到了「队列」,队列在 JUC 中也是多种多样存在,所以本文就以「远看」视角,帮助大家快速了解与区分这些看似「杂乱」的队列

并发队列

Java 并发队列按照实现方式来进行划分可以分为 2 种:

  1. 阻塞队列
  2. 非阻塞队列

如果你已经看完并发系列锁的实现,你已经能够知道他们实现的区别:

前者就是基于锁实现的,后者则是基于 CAS 非阻塞算法实现的

常见的队列有下面这几种:

瞬间懵逼?看到这个没有人性的图想直接走人? 客观先别急,一会就柳暗花明了

当下你也许有个问题:

为什么会有这么多种队列的存在?

锁有应对各种情形的锁,队列也自然有应对各种情形的队列了, 是不是也有点单一职责原则的意思呢?

所以我们要了解这些队列到底是怎么设计的?以及用在了哪些地方?

先来看下图

如果你在 IDE 中打开以上非阻塞队列和阻塞队列,查看其实现方法,你就会发现,阻塞队列较非阻塞队列 额外支持两种操作:

  1. 阻塞的插入 当队列满时,队列会阻塞插入元素的线程,直到队列不满
  2. 阻塞的移除 当队列为空时,获取元素的线程会阻塞,直到队列变为非空

综合说明入队/出队操作,看似杂乱的方法,用一个表格就能概括了

抛出异常

  • 当队列满时,此时如果再向队列中插入元素,会抛出 IllegalStateException (这很好理解)
  • 当队列空时,此时如果再从队列中获取元素,会抛出 NoSuchElementException (这也很好理解)

返回特殊值

  • 当向队列插入元素时,会返回元素是否插入成功,成功则返回 true
  • 当从队列移除元素时,如果没有则返回 null

一直阻塞

  • 当队列满时,如果生产者线程向队列 put 元素,队列会一直阻塞生产者线程,直到队列可用或者响应中断退出
  • 当队列为空时,如果消费者线程 从队列里面 take 元素,队列会阻塞消费者线程,直到队列不为空

关于阻塞,我们其实早在 并发编程之等待通知机制 就已经充分说明过了,你还记得下面这张图吗?原理其实是一样一样滴

超时退出

和锁一样,因为有阻塞,为了灵活使用,就一定支持超时退出,阻塞时间达到超时时间,就会直接返回

至于为啥插入和移除这么多种单词表示形式,我也不知道,为了方便记忆,只需要记住阻塞的方法形式即可:

单词 put 和 take 字母 t 首位相连,一个放,一个拿

到这里你应该对 Java 并发队列有了个初步的认识了,原来看似杂乱的方法貌似也有了规律。接下来就到了疯狂串知识点的时刻了,借助前序章节的知识,分分钟就理解全部队列了

ArrayBlockingQueue

之前也说过,JDK中的命名还是很讲究滴,一看这名字,底层就是数组实现了,是否有界,那就看在构造的时候是否需要指定 capacity 值了

填鸭式的说明也容易忘,这些都是哪看到的呢?在所有队列的 Java docs 的第一段,一句话就概括了该队列的主要特性,所以强烈建议大家自己在看源码时,简单瞄一眼 docs 开头,心中就有多半个数了

在讲 Java AQS队列同步器以及ReentrantLock的应用 时我们介绍了公平锁与非公平锁的概念,ArrayBlockingQueue 也有同样的概念,看它的构造方法,就有 ReentrantLock 来辅助实现

public ArrayBlockingQueue(int capacity, boolean fair) {
    if (capacity <= 0)
        throw new IllegalArgumentException();
    this.items = new Object[capacity];
    lock = new ReentrantLock(fair);
    notEmpty = lock.newCondition();
    notFull =  lock.newCondition();
}

默认情况下,依旧是不保证线程公平访问队列(公平与否是指阻塞的线程能否按照阻塞的先后顺序访问队列,先阻塞线访问,后阻塞后访问)

到这我也要临时问一个说过多次的面试送分题了:

为什么默认采用非公平锁的方式?它较公平锁方式有什么好处,又可能带来哪些问题?

知道了以上内容,结合上面表格中的方法,ArrayBlockingQueue 就可以轻松过关了

和数组相对的自然是链表了

LinkedBlockingQueue

LinkedBlockingQueue 也算是一个有界阻塞队列 ,从下面的构造函数中你也可以看出,该队列的默认和最大长度为 Integer.MAX_VALUE ,这也就 docs 说 optionally-bounded 的原因了

public LinkedBlockingQueue() {
    this(Integer.MAX_VALUE);
}

public LinkedBlockingQueue(int capacity) {
  if (capacity <= 0) throw new IllegalArgumentException();
  this.capacity = capacity;
  last = head = new Node<E>(null);
}

正如 Java 集合一样,链表形式的队列,其存取效率要比数组形式的队列高。但是在一些并发程序中,数组形式的队列由于具有一定的可预测性,因此可以在某些场景中获得更高的效率

看到 LinkedBlockingQueue 是不是也有些熟悉呢? 为什么要使用线程池? 就已经和它多次照面了

创建单个线程池

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

创建固定个数线程池

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

面试送分题又来了

使用 Executors 创建线程池很简单,为什么大厂严格要求禁用这种创建方式呢?

PriorityBlockingQueue

PriorityBlockingQueue 是一个支持优先级的无界的阻塞队列,默认情况下采用自然顺序升序排列,当然也有非默认情况自定义优先级,需要排序,那自然要用到 Comparator 来定义排序规则了

可以定义优先级,自然也就有相应的限制,以及使用的注意事项

按照上图说明,队列中不允许存在 null 值,也不允许存在不能排序的元素

对于排序值相同的元素,其序列是不保证的,但你可以继续自定义其他可以区分出来优先级的值,如果你有严格的优先级区分,建议有更完善的比较规则,就像 Java docs 这样

class FIFOEntry<E extends Comparable<? super E>>
  implements Comparable<FIFOEntry<E>> {
    static final AtomicLong seq = new AtomicLong(0);
    final long seqNum;
    final E entry;
    public FIFOEntry(E entry) {
    seqNum = seq.getAndIncrement();
    this.entry = entry;
  }
  public E getEntry() { return entry; }
  public int compareTo(FIFOEntry<E> other) {
    int res = entry.compareTo(other.entry);
    if (res == 0 && other.entry != this.entry)
    res = (seqNum < other.seqNum ? -1 : 1);
    return res;
  }
}

队列容量是没有上限的,但是如果插入的元素超过负载,有可能会引起OutOfMemory异常(这是肯定的),这也是为什么我们通常所说,队列无界,心中有界

PriorityBlockingQueue 也有 put 方法,这是一个阻塞的方法,因为它是无界的,自然不会阻塞,所以就有了下面比较聪明的做法

public void put(E e) {
    offer(e); // never need to block  请自行对照上面表格
}

可以给定初始容量,这个容量会按照一定的算法自动扩充

// Default array capacity.
private static final int DEFAULT_INITIAL_CAPACITY = 11;

public PriorityBlockingQueue() {
    this(DEFAULT_INITIAL_CAPACITY, null);
}

这里默认的容量是 11,由于也是基于数组,那面试送分题又来了

你通常是怎样定义容器/集合初始容量的?有哪些依据?

DelayQueue

DelayQueue 是一个支持延时获取元素的无界阻塞队列

  • 是否延时肯定是和某个时间(通常和当前时间) 进行比较
  • 比较过后还要进行排序,所以也是存在一定的优先级

看到这也许觉得这有点和 PriorityBlockingQueue 很像,没错,DelayQueue 的内部也是使用 PriorityQueue

上图绿色框线也告诉你,DelayQueue 队列的元素必须要实现 Depayed 接口:

所以从上图可以看出使用 DelayQueue 非常简单,只需要两步:

实现 getDelay() 方法,返回元素要延时多长时间

public long getDelay(TimeUnit unit) {
  	// 最好采用纳秒形式,这样更精确
    return unit.convert(time - now(), NANOSECONDS);
}

实现 compareTo() 方法,比较元素顺序

public int compareTo(Delayed other) {
    if (other == this) // compare zero if same object
        return 0;
    if (other instanceof ScheduledFutureTask) {
        ScheduledFutureTask<?> x = (ScheduledFutureTask<?>)other;
        long diff = time - x.time;
        if (diff < 0)
            return -1;
        else if (diff > 0)
            return 1;
        else if (sequenceNumber < x.sequenceNumber)
            return -1;
        else
            return 1;
    }
    long diff = getDelay(NANOSECONDS) - other.getDelay(NANOSECONDS);
    return (diff < 0) ? -1 : (diff > 0) ? 1 : 0;
}

上面的代码哪来的呢?如果你打开 ScheduledThreadPoolExecutor 里的 ScheduledFutureTask,你就看到了 (ScheduledThreadPoolExecutor 内部就是应用 DelayQueue)

所以综合来说,下面两种情况非常适合使用 DelayQueue

  • 缓存系统的设计:用 DelayQueue 保存缓存元素的有效期,使用一个线程循环查询 DelayQueue,如果能从 DelayQueue 中获取元素,说明缓存有效期到了
  • 定时任务调度:用 DelayQueue 保存当天会执行的任务以及时间,如果能从 DelayQueue 中获取元素,任务就可以开始执行了。比如 TimerQueue 就是这样实现的

SynchronousQueue

这是一个不存储元素的阻塞队列,不存储元素还叫队列?

没错,SynchronousQueue 直译过来叫同步队列,如果在队列里面呆久了应该就算是“异步”了吧

所以使用它,每个put() 操作必须要等待一个 take() 操作,反之亦然,否则不能继续添加元素

实际中怎么用呢?假如你需要两个线程之间同步共享变量,如果不用 SynchronousQueue 你可能会选择用 CountDownLatch 来完成,就像这样:

ExecutorService executor = Executors.newFixedThreadPool(2);
AtomicInteger sharedState = new AtomicInteger();
CountDownLatch countDownLatch = new CountDownLatch(1);

Runnable producer = () -> {
    Integer producedElement = ThreadLocalRandom
      .current()
      .nextInt();
    sharedState.set(producedElement);
    countDownLatch.countDown();
};

Runnable consumer = () -> {
    try {
        countDownLatch.await();
        Integer consumedElement = sharedState.get();
    } catch (InterruptedException ex) {
        ex.printStackTrace();
    }
};

这点小事就用计数器来实现,显然很不合适,用 SynchronousQueue 改造一下,感觉瞬间就不一样了

ExecutorService executor = Executors.newFixedThreadPool(2);
SynchronousQueue<Integer> queue = new SynchronousQueue<>();

Runnable producer = () -> {
    Integer producedElement = ThreadLocalRandom
      .current()
      .nextInt();
    try {
        queue.put(producedElement);
    } catch (InterruptedException ex) {
        ex.printStackTrace();
    }
};

Runnable consumer = () -> {
    try {
        Integer consumedElement = queue.take();
    } catch (InterruptedException ex) {
        ex.printStackTrace();
    }
};

其实 Executors.newCachedThreadPool() 方法里面使用的就是 SynchronousQueue

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

看到前面 LinkedBlockingQueue 用在 newSingleThreadExecutor 和 newFixedThreadPool 上,而newCachedThreadPool 却用 SynchronousQueue,这是为什么呢?

因为单线程池和固定线程池中,线程数量是有限的,因此提交的任务需要在LinkedBlockingQueue队列中等待空余的线程;

而缓存线程池中,线程数量几乎无限(上限为Integer.MAX_VALUE),因此提交的任务只需要在SynchronousQueue 队列中同步移交给空余线程即可, 所以有时也会说 SynchronousQueue 的吞吐量要高于 LinkedBlockingQueue 和 ArrayBlockingQueue

LinkedTransferQueue

简单来说,TransferQueue提供了一个场所,生产者线程使用 transfer 方法传入一些对象并阻塞,直至这些对象被消费者线程全部取出。

你有没有觉得,刚刚介绍的 SynchronousQueue 是否很像一个容量为 0 的 TransferQueue。

但 LinkedTransferQueue 相比其他阻塞队列多了三个方法

  • transfer(E e) 如果当前有消费者正在等待消费元素,transfer 方法就可以直接将生产者传入的元素立刻 transfer (传输) 给消费者;如果没有消费者等待消费元素,那么 transfer 方法会把元素放到队列的 tail(尾部)节点,一直阻塞,直到该元素被消费者消费才返回
  • tryTransfer(E e) tryTransfer,很显然是一种尝试,如果没有消费者等待消费元素,则马上返回 false ,程序不会阻塞
  • tryTransfer(E e, long timeout, TimeUnit unit) 带有超时限制,尝试将生产者传入的元素 transfer 给消费者,如果超时时间到,还没有消费者消费元素,则返回 false

你瞧,所有阻塞的方法都是一个套路:

  1. 阻塞方式
  2. 带有 try 的非阻塞方式
  3. 带有 try 和超时时间的非阻塞方式

看到这你也许感觉 LinkedTransferQueue 没啥特点,其实它和其他阻塞队列的差别还挺大的:

BlockingQueue 是如果队列满了,线程才会阻塞;但是 TransferQueue 是如果没有消费元素,则会阻塞 (transfer 方法)

这也就应了 Doug Lea 说的那句话:

LinkedTransferQueue is actually a superset of ConcurrentLinkedQueue, SynchronousQueue (in “fair” mode), and unbounded
LinkedBlockingQueues. And it's made better by allowing you to mix and
match those features as well as take advantage of higher-performance i
mplementation techniques.

简单翻译:

LinkedTransferQueue 是ConcurrentLinkedQueue, SynchronousQueue (在公平模式下), 无界的LinkedBlockingQueues等的超集; 允许你混合使用阻塞队列的多种特性

所以,在合适的场景中,请尽量使用LinkedTransferQueue

上面都看的是单向队列 FIFO,接下来我们看看双向队列

LinkedBlockingDeque

LinkedBlockingDeque 是一个由链表结构组成的双向阻塞队列,凡是后缀为 Deque 的都是双向队列意思,后缀的发音为deck——/dek/, 刚接触它时我以为是这个冰激凌的发音

所谓双向队列值得就是可以从队列的两端插入和移除元素。所以:

双向队列因为多了一个操作队列的入口,在多线程同时入队是,也就会减少一半的竞争

队列有头,有尾,因此它又比其他阻塞队列多了几个特殊的方法

  • addFirst
  • addLast
  • xxxxFirs
  • txxxxLast
  • ... ...

这么一看,双向阻塞队列确实很高效,

那双向阻塞队列应用在什么地方了呢?

不知道你是否听过 “工作窃取”模式,看似不太厚道的一种方法,实则是高效利用线程的好办法。下一篇文章,我们就来看看 ForkJoinPool 是如何应用 “工作窃取”模式的

总结

到这关于 Java 队列(其实主要介绍了阻塞队列)就快速的区分完了,将看似杂乱的方法做了分类整理,方便快速理解其用途,同时也说明了这些队列的实际用途。相信你带着更高的视角来阅读源码会更加轻松,最后也希望大家认真看两个队列的源码实现,在遇到队列的问题,脑海中的画面分分钟就可以搞定了

以上就是10分钟搞定Java并发队列的详细内容,更多关于Java并发队列的资料请关注我们其它相关文章!

(0)

相关推荐

  • 详解Java中的延时队列 DelayQueue

    当用户超时未支付时,给用户发提醒消息.另一种场景是,超时未付款,订单自动取消.通常,订单创建的时候可以向延迟队列种插入一条消息,到时间自动执行.其实,也可以用临时表,把这些未支付的订单放到一个临时表中,或者Redis,然后定时任务去扫描.这里我们用延时队列来做.RocketMQ有延时队列,RibbitMQ也可以实现,Java自带的也有延时队列,接下来就回顾一下各种队列. Queue 队列是一种集合.除了基本的集合操作以外,队列还提供了额外的插入.提取和检查操作.队列的每个方法都以两种形式存在:一

  • Java动态循环队列是如何实现的

    一.队列 1.1 定义 队列 (Queue) 是一种限定性的有序线性表,它只允许在表的一端插入元素,而在另一端删除元素,所以队列具有先进先出 (Fist In Fist Out,缩写为FIFO)的特性. 在队列中,允许插入的一端叫做队尾(rear): 允许删除的一端则称为队头(front). 队列是一个有序列表,可以用数组或是链表来实现. 遵循先进先出的原则.即:先存入队列的数据,要先取出. 1.2 抽象数据类型 数据元素:可以是任意类型的数据,但必须属于同一个数据对象. 关系:队列中数据元素之

  • Java 延迟队列的常用的实现方式

    延迟队列的使用场景还比较多,例如: 1.超时未收到支付回调,主动查询支付状态: 2.规定时间内,订单未支付,自动取消: ... 总之,但凡需要在未来的某个确定的时间点执行检查的场景中都可以用延迟队列. 常见的手段主要有:定时任务扫描.RocketMQ延迟队列.Java自动的延迟队列.监听Redis Key过期等等 1.  DelayQueue 首先,定义一个延迟任务 package com.cjs.example; import lombok.Data; import java.util.con

  • JAVA 实现延迟队列的方法

    延迟队列的需求各位应该在日常开发的场景中经常碰到.比如: 用户登录之后5分钟给用户做分类推送: 用户多少天未登录给用户做召回推送: 定期检查用户当前退款账单是否被商家处理等等场景. 一般这种场景和定时任务还是有很大的区别,定时任务是你知道任务多久该跑一次或者什么时候只跑一次,这个时间是确定的.延迟队列是当某个事件发生的时候需要延迟多久触发配套事件,引子事件发生的时间不是固定的. 业界目前也有很多实现方案,单机版的方案就不说了,现在也没有哪个公司还是单机版的服务,今天我们一一探讨各种方案的大致实现

  • java中用数组实现环形队列的示例代码

    本篇文章主要讲述了使用数组实现环形队列的思路以及具体代码 一.队列是什么 我们先来看下百科的解释: 队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,队列是一种操作受限制的线性表.进行插入操作的端称为队尾,进行删除操作的端称为队头. 总结起来两点: 1.一种线性表 2.添加操作只能在表尾,删除操作在表头(先进先出) 二.实现队列的思路 1.初始化一个空队列 初始化一个大小固定的数组,并将头指针,尾指针都指向下表为0的位置,但其

  • 详解java中的阻塞队列

    阻塞队列简介 阻塞队列(BlockingQueue)首先是一个支持先进先出的队列,与普通的队列完全相同: 其次是一个支持阻塞操作的队列,即: 当队列满时,会阻塞执行插入操作的线程,直到队列不满. 当队列为空时,会阻塞执行获取操作的线程,直到队列不为空. 阻塞队列用在多线程的场景下,因此阻塞队列使用了锁机制来保证同步,这里使用的可重入锁: 而对于阻塞与唤醒机制则有与锁绑定的Condition实现 应用场景:生产者消费者模式 java中的阻塞队列 java中的阻塞队列根据容量可以分为有界队列和无界队

  • SpringBoot集成JmsTemplate(队列模式和主题模式)及xml和JavaConfig配置详解

    1.导入jar包: <!--jmsTemplate--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-activemq</artifactId> </dependency> <dependency> <groupId>org.apache.activemq</g

  • 10分钟搞定Java并发队列

    前言 如果按照用途与特性进行粗略的划分,JUC 包中包含的工具大体可以分为 6 类: 执行者与线程池 并发队列 同步工具 并发集合 锁 原子变量 在并发系列中,主要讲解了 执行者与线程池,同步工具,锁 , 在分析源码时,或多或少的提及到了「队列」,队列在 JUC 中也是多种多样存在,所以本文就以「远看」视角,帮助大家快速了解与区分这些看似「杂乱」的队列 并发队列 Java 并发队列按照实现方式来进行划分可以分为 2 种: 阻塞队列 非阻塞队列 如果你已经看完并发系列锁的实现,你已经能够知道他们实

  • 10分钟搞定让你困惑的 Jenkins 环境变量过程详解

    前言 Jenkins, DevOps 技术栈的核心之一,CI/CD 离不开编写 Pipeline 脚本,上手 Jenkins ,简单查一下文档,你就应该不会被 agent,stages,step 这类关键词弄懵,也能很快构建出 pipeline 的骨架 但是当向骨架中填充内容的时候,尤其如何利用环境变量(系统内置 | 自定义),多数人都会变得比较混乱,浪费很多时间,本文就帮助大家快速通关环境变量 准备 如果你想一边阅读本文,一边实践,但是没有 Jenkins 服务可用,又想快速尝试,可以应用 D

  • 5分钟搞定java单例模式

    目录 单例模式 单例模式的运用场景 实现单例模式的方法思路 实现单例模式的方式 01懒汉单例式 02饿汉单列式 03静态内部类的方式 04枚举 资源加载和性能区别 单例模式 单例模式(Singleton),也叫单子模式,是一种常用的软件设计模式.在应用这个模式时,单例对象的类必须保证只有一个实例存在.许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为. 比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象

  • 带你快速搞定java并发库

    目录 一.总览 二.Executor总览 三.继承结构 四.怎么保证只有一个线程 五.怎么保证时间可以定时执行 六.使用 总结 一.总览 计算机程序 = 数据 + 算法. 并发编程的一切根本原因是为了保证数据的正确性,线程的效率性. Java并发库共分为四个大的部分,如下图 Executor 和 future 是为了保证线程的效率性 Lock 和数据结构 是为了维持数据的一致性. Java并发编程的时候,思考顺序为, 对自己的数据要么加锁.要么使用提供的数据结构,保证数据的安全性 调度线程的时候

  • JS组件系列之MVVM组件 vue 30分钟搞定前端增删改查

    正文 前言:关于Vue框架,好几个月之前就听说过,了解一项新技术之后,总是处于观望状态,一直在犹豫要不要系统学习下.正好最近有点空,就去官网了解了下,看上去还不错的一个组件,就抽空研究了下.最近园子里vue也确实挺火,各种入门博文眼花缭乱,博主也不敢说写得多好,就当是个学习笔记,有兴趣的可以看看. 一.MVVM大比拼 关于MVVM,原来在介绍knockout.js的时候有过讲解,目前市面上比较火的MVVM框架也是一抓一大把,比如常见的有Knockout.js.Vue.js.AvalonJS.An

  • 5分钟搞定Nginx安装的教程

    1. 安装gcc(centos 7之后一般已自带,可以在第6步失败后再安装) yum install gcc gcc-c++ 2. 安装pcre yum install -y pcre pcre-devel 3. 安装zlib yum install -y zlib zlib-devel 4. 安装openssl yum install -y openssl openssl-devel 5. 下载并解压Nginx(之后进入Nginx目录) wget http://nginx.org/downlo

  • 一文彻底搞定Java哈希表和哈希冲突

    一.什么是哈希表? 哈希表也叫散列表,它是基于数组的.这间接带来了一个优点:查找的时间复杂度为 O(1).当然,它的插入时间复杂度也是 O(1).还有一个缺点:数组创建后扩容成本较高. 哈希表中有一个"主流"思想:转换.一个重要的概念是将「键」或「关键字」转换成数组下标.这由"哈希函数"完成. 二.什么是哈希函数? 由上,其作用就是将非 int 的键/关键字转化为 int 的值,使可以用来做数组下标. 比如,HashMap 中就这样实现了哈希函数: static f

  • 一文教你搞定Java Optional类判空操作

    目录 概述 创建Optional实例 获取Optional中的值 判断Optional是否为空 Optional中的过滤.转换方法 概述 最近项目组内做code review,充斥着大量的.原始的.丑陋的判空语句,大致类似下面的代码: if (user != null) { Address address = user.getAddress(); if (address != null) { Country country = address.getCountry(); if (country

  • 一文搞懂Java并发AQS的共享锁模式

    目录 概述 自定义共享锁例子 核心原理机制 源码解析 成员变量 共享锁获取acquireShared(int) 共享释放releaseShared(int) 概述 这篇文章深入浅出理解Java并发AQS的独占锁模式讲解了AQS的独占锁实现原理,那么本篇文章在阐述AQS另外一个重要模式,共享锁模式,那什么是共享锁呢? 共享锁可以由多个线程同时获取, 比较典型的就是读锁,读操作并不会产生副作用,所以可以允许多个线程同时对数据进行读操作而不会有线程安全问题,jdk中的很多并发工具比如ReadWrite

  • 带你快速搞定java多线程(3)

    目录 一.锁的概念 二.synchronized 的使用方式 三.synchronized 的实现原理列 小结 四.线程池是什么 五.为什么要用线程池? 六.看下类图,从整体上理解下 七.线程池的创建 八.线程池核心参数说明 九.几个疑问点 9.1.是怎么保证线程不销毁的? 9.2 提交任务有哪几种方式? 9.3 拒绝策略都有哪些? 9.4 线程池的关闭 9.5 初始化线程池时线程数的选择 十.总结 一.锁的概念 先来聊聊这几个概念,总不能聊起来的时候啥也不知道,只知道干活也没有用. 公平锁:当

随机推荐