看了前两篇你肯定已经理解了 java 并发编程的低层构建。然而,在实际编程中,应该经可能的远离低层结构,毕竟太底层的东西用起来是比较容易出错的,特别是并发编程,既难以调试,也难以发现问题,我们还是使用由并发处理的专业人员实现的较高层次的结构要方便、安全得多。

阻塞队列

  对于许多线程问题,都可以使用一个或多个队列来安全、优雅的进行数据的传递。比如经典的生产者--消费者问题,生产者不停的生成某些数据,消费者需要处理数据,在多线程环境中,如何安全的将数据从生产者线程传递到消费者线程?

  无需使用锁和条件对象,java 自带的阻塞队列就能够完美的解决这个问题。阻塞队列中所有方法都是线程安全的,所以我们进行读取、写入操作时无需考虑并发问题。阻塞队列主要有以下几种方法:

方法 正常结果 异常结果
add 添加一个元素 队列满,抛出 IllegalStateException 异常
element 返回队列头元素 队列空,抛出 NoSuckElementException 异常
offer 添加一个元素,返回 true 队列满,返回 false
peek 返回队列的头元素 队列空,返回 null
poll 移出并返回队列头元素 队列空,返回 null
put 添加一个元素 队列满,阻塞
remove 移出并返回头元素 队列空,抛出 NoSuckElementException 异常
take 移出并返回头元素 队列空,则阻塞

上面的方法主要分成了三类,第一类:异常情况下抛出异常;第二类:异常情况返回 false/null;第三类:异常情况下阻塞。可以根据自身情况选择合适的方法来操作队列。

阻塞队列的实现

  在 java.util.concurrent 包中,提供了阻塞队列的几种实现,当前也可以自己实现 BlockingQueue 接口,实现自己的阻塞队列。

  • LinkdedBlockingQueue:链式阻塞队列。一般情况下链式的结构容量都是没有上限的,但是也可以选择手动指定最大容量。
  • LinkdedBlockingDeque:链式阻塞双端队列。
  • PriorityBlockingQueue:优先级队列。按照优先级移出,无容量上限。
  • ArrayBlockingQueue:数组队列,需指定容量。可选指定是否需要公平性,如果设置了公平性,等待了最长时间的线程会优先得到处理,但是会降低性能。

延迟队列

  DelayQueue 也是阻塞队列的一种,不过它要求队列中的元素实现Delayed接口。需要重新两个方法:

  • long getDelay(TimeUnit unit)返回延迟的时间,负值表示延迟结束,只有延迟结束的情况下,元素才能从队列中移出。
  • int compareTo(Delayed o)比较方法,DelayQueue 使用该方法对元素进行排序。

传递队列

  在 Java SE 7 中新增了一个 TransferQueue 接口,允许生产者等待,直到消费者消费了某个元素。原本生产者消费者是没有关系的,生产者并不知道某个元素是否被消费者消费了。通过此接口可以让生产者知道某个元素确实被消费了。如果生产者调用:

q.transer(item)

方法,这个调用会阻塞,知道 item 被消费线程取出消费。LinkedTransferQueue 实现了此接口。

线程安全的集合

  如果多个线程并发的操作集合,会很容易出现问题,我们可以选择锁来保护共享数据,但是更好的选择是使用线程安全的集合来作为替代。本节介绍 Java 类库中提供的线程安全的集合(上一节介绍的阻塞队列也在其中)。

  这类集合,size 是通过便利得出的,较慢。而且如果 size 数量大于 20 亿,有可能超过 int 的范围,使用 size 方法无法获取到大小,在 java8 中引入了 mappingCount 方法,返回值类型为 long。

映射 map

  映射是日常使用中非常常见的一种数据结构。共有以下几种线程安全的映射:

  • ConcurrentSkipListMap:有序映射,根据键排序
  • ConcurrentHashMap:无序映射

映射条目的原子更新

  一旦涉及到多线程环境,做啥都比较麻烦,比如更新一个 map 中某个键值对的值,下面的操作显然是不正确的:

int old = map.get(key);
map.put(key,old+1);

假如有两个线程同时操作一个 key,虽然 put 方法是线程安全的,但是由于两个线程之前读取的 old 是一样的,这样就会导致某个线程的修改被覆盖掉。

  有以下几种安全的更新方法:

  1. 使用 repalce(key,oldValue,newValue)方法,此方法会在 key,oldValue 完全匹配时将 oldValue 换为 newValue 返回 true,否则返回 false。
  2. 使用 AtomicLong 或者 LongAdder 作为映射的值,这两个的操作方法是原子性的,因此可以安全的修改值。 3.使用 compute 类似方法完成更新。比如下面的:
# 如果key不再map中,v的值为null
map.compute(key,(k,v)->v==null?1:v+1); # 如果不存在key
map.computeIfAbsent(key,key->new LongAdder()) # 如果存在key
map.computeIfPresent(key,key->key+1) # 和compute方法类似,不过不处理键
map.merge(key,value,(existingValue,newValue)->existingValue+newValue+1)

批操作

  java8 引入的,即使有其他线程在处理映射,批操作也能安全的执行。批操作会遍历映射,处理便利过程中找到的元素,且无需冻结当前映射的快照。显然通过批操作获取的结果不是完全精确的,因为遍历过程中,元素可能会被改变。

  有以下三种不同的操作:

  • 搜索(search),遍历结果直到返回一个非 null 的结果
  • 归约(reduce),组合所有键或值,需提供累加函数
  • forEach,遍历所有的键值对

    每个操作都有 4 个版本:
  • operationKeys:处理键
  • operationValues:处理值
  • operation:处理键值
  • operationEntries:处理需要 map.Entry 对象

并发集合

  线程安全的 set 集合只有以下一种:

  • ConcurrentSkipListSet:有序 set

    如果我们想要一个 hash 结构的,线程安全的 set,有以下几种办法.
  1. 通过 ConcurrentHashMap.<Key>newKeySet()生成一个 Set,比如:
Set<String> sets = ConcurrentHashMap.<String>newKeySet();

这其实只是 ConcurrentHashMap<Key,Boolean>的一个包装器,所有的值都为 true

  1. 通过现有映射对象的 keySet 方法,生成这个映射的键集。如果删除这个集的某个元素,映射上对于元素也会被删除。但是不能添加元素,因为没有相应的值。java8 新增了一个 keySet 方法,可以设置一个默认值,这样就能为向集合中增加元素。

数组

  在 Concurrent 包中只有一个CopyOnWriteArrayList数组。该数组所有的修改都会对底层数组进行复制,也就是每插入一个元素都会将原来的数组复制一份并加入新的元素。

  当构建一个迭代器时,迭代器指向的是当前数组的引用,如果后来数组被修改了,迭代器指向的任然是旧的数组。

任何集合类都可以通过使用同步包装器变成线程安全的,如下:

//线程安全的列表
List<String> list1 = Collections.synchronizedList(new ArrayList<>());
//线程安全的map
Map<String,String> map1 = Collections.synchronizedMap(new HashMap<>());
//线程安全的set
Set<String> set1 = Collections.synchronizedSet(new HashSet<>());

并行数组算法

  在 java 8 中,Arrays 类提供了大量的并行化操作。

  1. Arrays.parallelSort

  对一个基本数据类型或对象的数组进行排序

  1. Arrays.paralletSetAll

  用一个函数计算得到的值填充一个数组。这个函数接收元素索引,然后计算值。例如:

# 将所有值加上对于的序号
Arrays.parallelSetAll(arr,i->i+ arr[i]);
  1. parallelPrefix

  用对应一个给定结合操作的前缀的累加结果替换各个数组元素。看文字描述不太容易看懂,这里用一个例子说明:

int[] arr = {1,2,3,4}
Arrays.parallelPrefix(arr,(x,y)->x*y);
// arr变成:[1,1*2,1*2*3,1*2*3*4]

本文原创发布与::https://www.tapme.top/blog/detail/2019-04-10

最全java多线程总结3——了解阻塞队列和线程安全集合不的更多相关文章

  1. JAVA多线程提高十二:阻塞队列应用

    一.类相关属性 接口BlockingQueue<E>定义: public interface BlockingQueue<E> extends Queue<E> { ...

  2. JAVA多线程学习十五 - 阻塞队列应用

    一.类相关属性 接口BlockingQueue<E>定义: public interface BlockingQueue<E> extends Queue<E> { ...

  3. java多线程(8)---阻塞队列

    阻塞队列 再写阻塞列队之前,我写了一篇有关queue集合相关博客,也主要是为这篇做铺垫的. 网址:[java提高]---queue集合  在这篇博客中我们接触的队列都是非阻塞队列,比如Priority ...

  4. “全栈2019”Java多线程第二十二章:饥饿线程(Starvation)详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  5. “全栈2019”Java多线程第十二章:后台线程setDaemon()方法详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  6. Java 多线程基础(十)interrupt()和线程终止方式

    Java 多线程基础(十)interrupt()和线程终止方式 一.interrupt() 介绍 interrupt() 定义在 Thread 类中,作用是中断本线程. 本线程中断自己是被允许的:其它 ...

  7. Java多线程总结(二)锁、线程池

    掌握Java中的多线程,必须掌握Java中的各种锁,以及了解Java中线程池的运用.关于Java多线程基础总结可以参考我的这篇博文Java多线程总结(一)多线程基础 转载请注明出处——http://w ...

  8. JAVA多线程-内存模型、三大特性、线程池

    一.线程的三大特性 原子性.可见性.有序性 1)原子性,即一个操作或者多个操作要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行.原子性其实就是保证数据一致.线程安全一部分. 2)可见性,即 ...

  9. 【系列】Java多线程初学者指南(1):线程简介

    原文地址:http://www.blogjava.net/nokiaguy/archive/2009/nokiaguy/archive/2009/03/archive/2009/03/19/26075 ...

随机推荐

  1. VAssist 使用技巧(函数声明定位,比VS的还要强大)

    1. 有了VAX可以关掉C++导航栏,快捷键ALT+M,显示当前打开文档的所有符号,而且可以输入进行过滤 2. 查找文件,shift+alt+o (直接定位) 3. 查找符号shift+alt+s 4 ...

  2. Information centric network (icn) node based on switch and network process using the node

    The present invention relates to an apparatus for supporting information centric networking. An info ...

  3. C/C++回调方式系列之一 函数指针和函数回调模式

    一.函数指针 1. 函数的定义 return_type function_name(parameter list) { function_body } return_type: 返回值,函数一定有返回 ...

  4. Qt5该插件机制(7)--插件开发演示示例代码(Lower-level API)

    插件代码 接口类的头文件 MyPluginInterface.h #ifndef INTERFACES_H #define INTERFACES_H #include <QtPlugin> ...

  5. WPF 界面实现多语言支持 中英文切换 动态加载资源字典

    1.使用资源字典,首先新建两个字典文件en-us.xaml.zh-cn.xaml.定义中英文的字符串在这里面[注意:添加xmlns:s="clr-namespace:System;assem ...

  6. 关于 IIS 上运行 ASP.NET Core 站点的“HTTP 错误 500.19”错误

    昨天回答了博问中的一个问题 —— “HTTP 错误 500.19 - Internal Server Error dotnetcore”,今天在这篇随笔中时候事后诸葛亮地小结一下. 服务器是 Wind ...

  7. WPF BorderBrush BorderThickness

    基本上所有的控件都可以设置BorderBrush BorderThickness 例如TextBox,Button

  8. sql count(1)不要和查询数据混用 非常耗时

    count(1)不要和查询数据混用 非常耗时 例子: SELECT w.[PKID], COUNT(1) OVER() AS TotalCount FROM w WITH(NOLOCK) INNER ...

  9. AY写给国人的教程- VS2017 Live Unit Testing[2/2]-C#人爱学不学-aaronyang技术分享

    原文:AY写给国人的教程- VS2017 Live Unit Testing[2/2]-C#人爱学不学-aaronyang技术分享 谢谢大家观看-AY的 VS2017推广系列 Live Unit Te ...

  10. Delphi调试activex

    以前好多次遇到了activex无法调试的问题,一直没搞清楚原因,最近终于搞清楚了,原来是IE由单线程变成了多线程. 下面就说说调试activex的方法 一.简单的方式,这种方式只适用于浏览器为单线程的 ...