Java多线程并发编程中并发容器第二篇之List的并发类讲解
概述
本文我们将详细讲解list对应的并发容器以及用代码来测试ArrayList、vector以及CopyOnWriteArrayList在100个线程向list中添加1000个数据后的比较
本文是《凯哥分享Java并发编程之J.U.C包讲解》系列教程中的第六篇。如果想系统学习,凯哥(kaigejava)建议从第一篇开始看。
从本篇开始,我们就来讲解讲解Java的并发容器。大致思路:先介绍什么是并发容器。然后讲解list相关的、map相关的以及队列相关的。这个系列会有好几篇文章。大家最好跟着一篇一篇学。
联系凯哥:
个人博客:www.kaigejava.com
公众号:凯哥Java(kaigejava)
正文开始
并发容器分类讲解
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());
}
相关推荐
在当今高度发展的信息技术领域中,随着硬件技术的进步和软件架构设计的复杂化,多线程与并发编程成为提高程序执行效率、增强系统性能的关键技术之一。本篇文章将围绕Java多线程与并发编程的核心概念和技术进行深入...
多线程并发查询允许我们将一个大任务分解为多个小任务,每个任务在不同的线程上独立运行,从而提高查询效率。在数据库查询中,这通常适用于处理分页数据或执行并行查询。 ### 2. 线程池 Java中的线程池是通过`java....
通过以上方法,我们可以在Java中有效地利用多线程处理数据库数据,提高程序的并发能力和效率。记得在设计时充分考虑线程间的协作与同步,以及数据库连接的管理和优化,以确保程序的稳定性和性能。
并行容器是 Java 多线程编程中不可或缺的一部分,它们为开发者提供了在高并发环境中高效、安全地管理和操作数据的工具。并发容器的出现解决了传统同步容器在性能上的不足,通过引入更先进的并发控制策略,如锁分段、...
在Java编程中,多线程技术是处理大数据批量导入或导出的重要手段。它能有效提升程序执行效率,尤其在数据库操作这样的I/O密集型任务中。本项目以"java多线程实现大批量数据导入源码"为题,旨在通过多线程策略将大量...
该资源是多线程并发下的单例模式-源码,几乎包含了所有方式实现的单例模式,并且能够确保在多线程并发下的线程安全性。 读者可结合本人博客 http://blog.csdn.net/cselmu9?viewmode=list 中的《线程并发之单例模式...
Java并发编程 背景介绍 并发历史 必要性 进程 资源分配的最小单位 线程 CPU调度的最小单位 线程的优势 (1)如果设计正确,多线程程序可以通过提高处理器资源的利用率来提升系统吞吐率 ...
在Java编程中,多线程技术是实现并发执行任务的关键,尤其在开发高效能的应用程序时,如模拟电影院的购票系统。在这个系统中,我们利用Java的多线程特性来模拟多个用户同时购票的情况,确保系统的高并发处理能力。...
以上就是本文的主要知识点,涉及到JAVA中的多线程编程、线程的创建与管理、集合的分割操作以及简单的面向对象设计。对于需要学习JAVA并发编程和网络编程的开发者来说,本文提供了一个很好的实例来加深理解,并且可以...
总结来说,Java中的多线程FTP上传和下载涉及网络通信、多线程编程以及错误处理等多个方面。Apache Commons Net库提供了便利的API,使得开发者可以高效地实现这些功能。在实践中,要注意线程安全和异常处理,以确保...
这是最基础也是最常见的实现多线程的方式之一。通过继承`Thread`类并重写`run()`方法来定义线程的行为。 ```java class MyThread extends Thread { public void run() { // 线程执行的代码 } } ``` ##### 2.2 ...
### Java高级之并发编程 #### 1. Java 8 新特性 ##### 1.1 Lambda 表达式 Java 8 引入了一个新的语法结构:“->”操作符,它将 Lambda 表达式分为两个部分: - **左侧**:Lambda 表达式的参数列表。 - **右侧**...
在Java编程中,多线程安全集合是程序员在并发环境下处理数据共享时必须考虑的关键概念。这些集合确保了在多个线程访问时的数据一致性、完整性和安全性,避免了竞态条件、死锁和其他并发问题。Java提供了一系列的线程...
多线程在Java中是通过Thread类和Runnable接口实现的。每个线程都有自己的执行路径,可以并发执行,提高程序效率。为了保证线程安全,Java提供了synchronized关键字、 volatile变量、ReentrantLock等机制来控制对共享...
Java是世界上最流行的编程语言之一,尤其在企业级应用开发中占据主导地位。本资源包“java基础(多线程,IO,集合,网络编程,泛型)”提供了对Java核心技术的全面介绍,包括五个核心主题:多线程、输入/输出(IO)、集合...
最后,"并发List、Set、ConcurrentHashMap底层原理剖析"(文件"03")将讨论Java中常用的并发容器,如CopyOnWriteArrayList、CopyOnWriteArraySet和ConcurrentHashMap。这些容器采用了不同的策略来保证线程安全,如...
在Java编程语言中,线程安全的容器是并发编程中不可或缺的部分。本课程"09、并发容器(Map、List、Set)实战及其原理"深入探讨了如何在多线程环境下有效使用Map、List和Set这三种核心数据结构。下面我们将详细讲解...
本文将深入探讨Java中的基础概念,包括多线程、反射以及泛型,这些都是Java编程中至关重要的知识点。 首先,我们来讨论Java多线程。多线程允许程序同时执行多个独立的任务,极大地提高了应用程序的效率和响应性。在...
在Java并发编程中,`CountDownLatch`是一个同步辅助类,它允许多个线程等待其他线程完成操作。在批量插入数据的场景下,可以创建一个CountDownLatch对象,初始化为线程的数量,每个线程处理完自己的数据后调用`...
Java并发容器是Java多线程编程中的重要工具,它们提供了高效、线程安全的数据结构,使得在并发环境下处理数据变得更加简单。在`java.util.concurrent`包中,有四种主要的并发容器类型:队列(BlockingQueue)、Map...