Java多线程并发编程中并发容器第二篇之List的并发类讲解

概述

本文我们将详细讲解list对应的并发容器以及用代码来测试ArrayList、vector以及CopyOnWriteArrayList在100个线程向list中添加1000个数据后的比较

本文是《凯哥分享Java并发编程之J.U.C包讲解》系列教程中的第六篇。如果想系统学习,凯哥(kaigejava)建议从第一篇开始看。

从本篇开始,我们就来讲解讲解Java的并发容器。大致思路:先介绍什么是并发容器。然后讲解list相关的、map相关的以及队列相关的。这个系列会有好几篇文章。大家最好跟着一篇一篇学。

正文开始

并发容器分类讲解

CopyOneWriteArrayList

Copy-One-Write:即写入时候复制。

我们知道在原来List子类中vactor是同步容器线程安全的。这个CopyOneWriteArrayList可以理解为是他的并发替代品。

其底层数据结构也是数值。和ArrayList的不同之处就在于:在list对象中新增或者是删除元素的时候会把原来的集合copy一份,增删操作是在新的对象中操作的。操作完成之后,会将新的数组替换原来的数组。

我们来看看CopyOnWriteArrayList源码中的add方法:

编辑

我们来看看setArray方法:

编辑

发现了吗?变量使用的是transient和volatile两个关键之来修饰的。

在之前文章中,我们知道了volatile关键字是内存可见性。那么transient关键字是干嘛的呢?我们来看下百科解释:

编辑

关键的一句话:用transient关键字修饰的成员变量不用参与序列化过程。

添加注释后的源码:

编辑

public boolean add(E e) {

final ReentrantLock lock = this.lock;

//获取到锁

lock.lock();

try {

//获取到原集合

Object[] elements = getArray();

int len = elements.length;

//将原集合copy一份到新的集合中。并设置新的集合的长度为原集合长度+1

Object[] newElements = Arrays.copyOf(elements, len + 1);

//将需要新增的元数添加到新的素组中

newElements[len] = e;

//将新数组替换原来数据。 使用transient和volatitle关键字修饰的

setArray(newElements);

return true;

} finally {

lock.unlock();

}

}

代码很简单,大致流程如下:

先从ReentrantLock中获取到锁(这样在多线程下可以防止其他线程来修改容器list里面内容了);

通过arrays.copyOf方法copy出一份原有数组长度+1;

将要添加的元素赋值给copy出来的数组;

使用setArray方法将新得数组替换原有素组。

因为都是List集合。我们就拿ArrayList、vector以及CopyOnWriteArrayList进行比较:

ArrayList、vector以及CopyOnWriteArrayList比较

业务场景描述:

启动100个线程,向对象中添加1000个数据。查看各自运行结果耗时及插入数据总数。代码在文章最后凯哥会贴出来。

先用线程非安全的arrayList执行效果:

编辑

执行arryList时间为 : 112毫秒!

List.size() : 93266

我们发现list的长度不对。正确的应该是100*1000.从结果来看,arrayList丢数据了。

使用Vector执行后的效果:

编辑

执行vector时间为 : 98毫秒!

List.size() : 100000

执行的总数对,说下同步锁没有丢数据。

在来看看copyOnWriteArrayList执行效果:

编辑

执行copyOnWriteArrayList时间为 : 5951毫秒!

List.size() : 100000

运行后数据比较:

运行的类

运行时长

运行结果

ArrayList

112毫秒

93266

Vector

98毫秒

100000

copyOnWriteArrayList

5951毫秒

100000

从上面表格中我们可以看出非安全线程的容器会丢数据。使用copyOneWriteArrayList耗时很长。那是因为每次运行都要copyof一份。

总结

copyArrayList(这里凯哥就简写了):是读写分离的。在写的时候会复制一个新的数组来完成插入和修改或者删除操作之后,再将新的数组给array.读取的时候直接读取最新的数据。

因为在写的时候需要向主内存申请控件,导致写操作的时候,效率非常低的(虽然在操作时候比较慢得,但是在删除或者修改数组的头和尾的时候还是很快的。因为其数据结构决定查找头和尾快,而且执行不需要同步锁)

从上面表中,可以看出copyArrayList虽然保证了线程的安全性,但是写操作效率太low了。但是相比Vector来说,在并发安全方面的性能要比vector好;

CopyArrayList和Vector相比改进的地方:

Vector是在新增、删除、修改以及查询的时候都使用了Synchronized关键字来保证同步的。但是每个方法在执行的时候,都需要获取到锁,在获取锁等待的过程中性能就会大大的降低的。

CopyArrayList的改进:只是在新增和删除的方法上使用了ReentrantLock锁进行(这里凯哥就不截图源码了,自己可以看看源码)。在读的时候不加锁的。所以在读的方面性能要不vector性能要好。

所以,CopyArrayList支持读多写少的并发情况

CopyOnWriteArrayList的使用场景:

由于读操作不加锁,增删改三个操作加锁的,因此适用于读多写少的场景,

局限性:因为读的时候不加锁的,读的效率和普通的arrayList是一样的。但是请看读操作:

编辑

编辑

在get的时候array使用的是volatile修饰的。是内存可见的。所以可以说copyArrayList在读的时候不会出现arrayList读取到脏数据的问题。

Get(i)方法比较如下:

联系凯哥:
个人博客:www.kaigejava.com
公众号:凯哥Java(kaigejava)

附件:arrayList、vector、copyOnwriteArrayList比较的代码:

public static void main(String[] args) {
//使用线程不安全的arrayList
// List<String> arryList = new ArrayList<>();
//使用vector
// List<String> arryList = new Vector<>();


//使用copyOnWriteArrayList
List<String> arryList = new CopyOnWriteArrayList<>();

Random random = new Random();
Thread [] threadArr = new Thread[100];
CountDownLatch latch = new CountDownLatch(threadArr.length);
Long beginTime = System.currentTimeMillis();
for(int i = 0;i<threadArr.length;i++){
threadArr[i] = new Thread(new Runnable() {
@Override
public void run() {
for(int j = 0; j < 1000; j++){
try {
arryList.add("value" + random.nextInt(100000));
} catch (Exception e) {

}
}
latch.countDown();
}
});
}
for(Thread t : threadArr){
t.start();
}
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
long endTime = System.currentTimeMillis();
System.out.println("执行copyOnWriteArrayList时间为 : " + (endTime-beginTime) + "毫秒!");
System.out.println("List.size() : " + arryList.size());
}

Java多线程并发编程中并发容器第二篇之List的并发类讲解的更多相关文章

  1. 【Java并发编程】6、volatile关键字解析&内存模型&并发编程中三概念

    volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在Java 5之后,volatile关键字才得以 ...

  2. Java并发编程中的相关注解

    引自:http://www.cnblogs.com/phoebus0501/archive/2011/02/21/1960077.html Java并发编程中,用到了一些专门为并发编程准备的 Anno ...

  3. volatile关键字解析&内存模型&并发编程中三概念

    原文链接: http://www.cnblogs.com/dolphin0520/p/3920373.html volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java5之前,它是一个 ...

  4. Java的并发编程中的多线程问题到底是怎么回事儿?

    在我之前的一篇<再有人问你Java内存模型是什么,就把这篇文章发给他.>文章中,介绍了Java内存模型,通过这篇文章,大家应该都知道了Java内存模型的概念以及作用,这篇文章中谈到,在Ja ...

  5. Java多线程学习(七)并发编程中一些问题

    本节思维导图: 关注微信公众号:"Java面试通关手册" 回复"Java多线程"获取思维导图源文件和思维导图软件. 多线程就一定好吗?快吗?? 并发编程的目的就 ...

  6. Java并发编程中的设计模式解析(二)一个单例的七种写法

    Java单例模式是最常见的设计模式之一,广泛应用于各种框架.中间件和应用开发中.单例模式实现起来比较简单,基本是每个Java工程师都能信手拈来的,本文将结合多线程.类的加载等知识,系统地介绍一下单例模 ...

  7. Java并发编程中的若干核心技术,向高手进阶!

    来源:http://www.jianshu.com/p/5f499f8212e7 引言 本文试图从一个更高的视角来总结Java语言中的并发编程内容,希望阅读完本文之后,可以收获一些内容,至少应该知道在 ...

  8. Java并发编程:同步容器

    Java并发编程:同步容器 为了方便编写出线程安全的程序,Java里面提供了一些线程安全类和并发工具,比如:同步容器.并发容器.阻塞队列.Synchronizer(比如CountDownLatch). ...

  9. 并发编程中.net与java的一些对比

    Java在并发编程中进行使用java.util.concurrent.atomic来处理一些轻量级变量 如AtomicInteger AtomicBoolean等 .Net中则使用Interlocke ...

  10. 【转】Java并发编程:同步容器

    为了方便编写出线程安全的程序,Java里面提供了一些线程安全类和并发工具,比如:同步容器.并发容器.阻塞队列.Synchronizer(比如CountDownLatch).今天我们就来讨论下同步容器. ...

随机推荐

  1. SpringMVC springmvc.xml配置路径前缀和后缀

    web.xml <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi=" ...

  2. UE Spline 样条网格体组件添加碰撞

    最近做的一个功能是通过Spline 生成管道模型. 如下图所示: 遇到的一个问题是需要给生成的管路加上碰撞.其中需要两个重要的步骤: 设置SplineMeshComponent的碰撞预设 找到&quo ...

  3. 超级炫酷的终端神器 eDEX-UI

    目录 eDEX-UI主要亮点: 优点: 软件简介 安装 Linux Windows 效果 更换皮肤 matrix Tron-disrupted 退出 常见问题解答 eDEX-UI,不仅是一款全屏幕.跨 ...

  4. PHP 程序员为什么依然是外包公司的香饽饽?

    大家好,我是码农先森. PHP 唯一的爽点就是开发起来「哇真快」这刚好和外包公司的需求相契合,在 Web 领域的芒荒年代 PHP 以王者姿态傲视群雄.如果 PHP 敢说第二,就没有哪门子语言敢称第一, ...

  5. PowerShell 基本使用

    PowerShell Basic PowerShell Basic 简要 基本使用 PowerShell cmdlet 获取帮助 PowerShell 别名和参数 编写一个 PowerShell 脚本 ...

  6. Flink 内存配置学习总结

    设置进程内存(Process Memory) Apache Flink通过严格控制其各种组件的内存使用,在JVM之上提供高效的工作负载. 配置总内存(Total Memory) Flink JVM进程 ...

  7. Django 文件导入实现方案

    Django文件导入实现方案 by:授客 QQ:1033553122 开发环境   Win 10   Python 3.5.4   Django-2.0.13.tar.gz 官方下载地址: https ...

  8. Spring Boot快速入门(二)搭建javaWeb项目

    1.配置pom.xml 教程一创建的项目为maven项目,所以搭建一个Spring Boot的Web项目,先导入一下jar包:即在pom.xml以下依赖: 1 <dependencies> ...

  9. python中基于tcp协议与udp的通信

    python中基于tcp协议与udp的通信(数据传输)   一.TCP协议介绍 流式协议(以数据流的形式通信传输) 安全协议(收发信息都需收到确认信息才能完成收发,是一种双向通道的通信) tcp协议在 ...

  10. 计算机论文中的SD SE是什么 ?

    Standard Deviation (SD) and Standard Error (SE): 标准差和标准误差:在统计学和数据分析中常用来描述数据的分布和估计值的精确性.SD 表示标准差,SE 表 ...