最全java多线程总结3——了解阻塞队列和线程安全集合不

时间:2023-02-06 13:49:33

  看了前两篇你肯定已经理解了 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多线程提高十二&colon;阻塞队列应用

    一.类相关属性 接口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. &OpenCurlyDoubleQuote;全栈2019”Java多线程第二十二章:饥饿线程(Starvation)详解

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

  5. &OpenCurlyDoubleQuote;全栈2019”Java多线程第十二章:后台线程setDaemon&lpar;&rpar;方法详解

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

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

    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. 利用servlet3&period;0上传,纯原生上传,不依赖任何第三方包

    tomcat7里面自带的servlet3.0.jar,支持很多新特性,例如,annotation配置servlet,上传,异步等等.... 如果你的tomcat版本低于7的话,单独在项目中引入serv ...

  2. ubuntu安装miniconda

    系统:ubuntu15.04   64位 wget -c http://repo.continuum.io/miniconda/Miniconda-latest-Linux-x86_64.sh chm ...

  3. 免解压版的Mysql的启动脚本,并且执行导入(windows)

    @echo off rem ################### set MYSQL_VERSION=mysql-5.5.32-win32 set LOCK=wot.lock rem ####### ...

  4. 机器学习技法:14 Radial Basis Function Network

    Roadmap RBF Network Hypothesis RBF Network Learning k-Means Algorithm k-Means and RBF Network in Act ...

  5. SVN与eclipse整合与使用、SVN与Apache整合

    SVN与eclipse整合 下载SVN插件(http://subclipse.tigris.org) http://subclipse.tigris.org/servlets/ProjectDocum ...

  6. 性能测试十七:liunx下jmeter结果报表、html报表

    控制台日志,只能看出整体的数据,若只是测单接口,这个最好用,但若有多个接口时,无法分别展示,所以需要换另外一种报表 四种方式来获取Jmeter的结果报表 一.在GUI模式下跑Jmeter的脚本,用tp ...

  7. jdk动态代理和cglib动态代理

    参考: http://www.importnew.com/22015.html Java动态代理 上面的代码运行的结果为: I'm proxy! Welcome oschina hosee's blo ...

  8. 表格排序(tablesorter)

    1.在html页面的head中引用 <script src="/static/Bootstrap/js/jquery/jquery.tablesorter.min.js"&g ...

  9. LeetCode222 Count CompleteTree Nodes&lpar;计算全然二叉树的节点数&rpar; Java 题解

    题目: Given a complete binary tree, count the number of nodes. Definition of a complete binary tree fr ...

  10. TCP为什么是三次握手,为什么不是两次或者四次 &amp&semi;&amp&semi; TCP四次挥手

    这是一个很有意思的问题~ 首先,我们要知道TCP是全双工的,即客户端在给服务器端发送信息的同时,服务器端也可以给客户端发送信息.而半双工的意思是A可以给B发,B也可以给A发,但是A在给B发的时候,B不 ...