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. awk脚本结合shell使用

    需求:判断hadoop用户是否存在**************************************************#!/bin/bashresult=`awk -F ": ...

  2. 严格根号带修 RMQ

    其实很简单,把之前随机数据的解法中维护块内数据的数据结构换成约束 RMQ,这样子复杂度 严格 单点修改 \(O(\sqrt n)\),区间查询 \(O(1)\),线性空间. 唯一的问题是常数太大了,有 ...

  3. 基于FileZilla上传、下载服务器数据的方法

      本文介绍FileZilla软件的下载.配置与使用方法.   在之前的博客中,我们提到了下载高分遥感影像数据需要用到FTP(文件传输协议,File Transfer Protocol)软件FileZ ...

  4. 委托之Action与Func

    1 例程代码: using System; using System.Collections.Generic; using System.Linq; using System.Text; using ...

  5. git 更新某个目录或文件

    不多说直接贴代码 更新文件 $ git fetch remote: Counting objects: 8, done. remote: Compressing objects: 100% (3/3) ...

  6. Linux 提权-NFS 共享

    本文通过 Google 翻译 NFS Share no_root_squash – Linux Privilege Escalation 这篇文章所产生,本人仅是对机器翻译中部分表达别扭的字词进行了校 ...

  7. Docker Compose 基本概要

    Docker Compose 基本概要 Compose 是一个用于定义和运行多容器 Docker 应用程序的工具.使用 YAML 文件来配置多个应用程序的服务,包括生产.暂存.开发.测试以及 CI 工 ...

  8. 如何在mysql中删除重复数据

    #分组去重法 讲重复的列进行分组 之后用min(id) #取其中最小的保留,其余的删除 -- 步骤 1: 创建临时表,保存每组最小的ID CREATE TEMPORARY TABLE tmp_keep ...

  9. 【Mybatis】07 万用Map & 模糊查询细节

    这应该算一个补充点 在尚硅谷的Java笔记资料有专门的一起万用Map讲解 参考自:https://www.bilibili.com/video/BV1NE411Q7Nx?p=5 万用Map 我们可以使 ...

  10. 【Ubuntu】下载安装 12.04.5版本 桌面端

    下载 Download 我擦早上起来才发现自己装的是远古版本了,唉算了,反正也没见过,就当是学个东西把 [下载地址:点我访问] https://releases.ubuntu.com/?_ga=2.2 ...