Java多线程并发编程中并发容器第二篇之List的并发类讲解
Java多线程并发编程中并发容器第二篇之List的并发类讲解
概述
本文我们将详细讲解list对应的并发容器以及用代码来测试ArrayList、vector以及CopyOnWriteArrayList在100个线程向list中添加1000个数据后的比较
本文是《凯哥分享Java并发编程之J.U.C包讲解》系列教程中的第六篇。如果想系统学习,凯哥(kaigejava)建议从第一篇开始看。
从本篇开始,我们就来讲解讲解Java的并发容器。大致思路:先介绍什么是并发容器。然后讲解list相关的、map相关的以及队列相关的。这个系列会有好几篇文章。大家最好跟着一篇一篇学。
联系凯哥:
个人博客:www.kaigejava.com
正文开始
并发容器分类讲解
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)方法比较如下:
附件: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());
}
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
Java多线程并发之同步容器和并发容器-第一篇
Java多线程并发之同步容器和并发容器-第一篇 概述 本文主要讲解在Java多线程并发开发中,集合中有哪些支持并发的的。什么是同步容器(集合),什么是并发容器(集合)?并发容器分类有哪些?每个分类都有哪些类? 本文是《凯哥分享Java并发编程之J.U.C包讲解》系列教程中的第五篇。如果想系统学习,凯哥(kaigejava)建议从第一篇开始看。 从本篇开始,我们就来讲解讲解Java的并发容器。大致思路:先介绍什么是并发容器。然后讲解list相关的、map相关的以及队列相关的。这个系列会有好几篇文章。大家最好跟着一篇一篇学。 正文开始 回顾Java中常用的容器(集合): 我们知道Java内部的容器类是集合相关的。主要包括:List接口、Map接口以及Set接口及其子类。 Collection及Map体系如下图: 编辑 我们常用的类关系简化版: 编辑 上图中少了一个类:Properties.这个类用的比较少。 编辑 我们来简单复习下Java中集合子类及数据结构: 顶级接口 二级接口 子类 数据结构 Collection List Arraylist 顺序结构动态数组类【数组结构】 Linke...
- 下一篇
码侬进阶Java架构师,需要掌握哪些技能?
架构师是什么?是一个既需要掌控整体又需要洞悉局部瓶颈并依据具体的业务场景给出解决方案的团队领导型人物。一个架构师得需要足够的想像力,能把各种目标需求进行不同维度的扩展,为目标客户提供更为全面的需求清单。所谓架构师,思考的是全局的东西,是如何组织你的系统,以达到业务要求,性能要求,具备可扩展性(scalability),可拓展性(extendability),前后兼容性等。可能涉及到的东西包括了从硬件到软件的方方面面。 架构师在软件开发的整个过程中起着很重要的作用。架构师的主要任务不是从事具体的软件程序的编写,而是从事更高层次的开发构架工作。他必须对开发技术非常了解,并且需要有良好的组织管理能力。可以这样说,一个架构师工作的好坏决定了整个软件开发项目的成败。在成为Java架构师之前,应当先成为Java工程师。熟练使用各种框架,并知道它们实现的原理。jvm虚拟机原理、调优,懂得jvm能让你写出性能更好的代码;池技术,什么对象池,连接池,线程池……Java反射技术,写框架必备的技术,但是有严重的性能问题,替代方案java字节码技术;nio,没什么好说的,值得注意的是"直接内存"的特点,使用场...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS7,8上快速安装Gitea,搭建Git服务器
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- CentOS关闭SELinux安全模块
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- CentOS8编译安装MySQL8.0.19
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- CentOS8安装Docker,最新的服务器搭配容器使用
- CentOS7,CentOS8安装Elasticsearch6.8.6
- Red5直播服务器,属于Java语言的直播服务器