欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

ArrayList源码和多线程安全问题分析

程序员文章站 2024-02-22 16:25:04
1.arraylist源码和多线程安全问题分析 在分析arraylist线程安全问题之前,我们线对此类的源码进行分析,找出可能出现线程安全问题的地方,然后代码进行验证...

1.arraylist源码和多线程安全问题分析

在分析arraylist线程安全问题之前,我们线对此类的源码进行分析,找出可能出现线程安全问题的地方,然后代码进行验证和分析。

1.1 数据结构

arraylist内部是使用数组保存元素的,数据定义如下:

transient object[] elementdata; // non-private to simplify nested class access

在arraylist中此数组即是共享资源,当多线程对此数据进行操作的时候如果不进行同步控制,即有可能会出现线程安全问题。

1.2 add方法可能出现的问题分析

首先我们看一下add的源码如下:

public boolean add(e e) {
ensurecapacityinternal(size + 1);
elementdata[size++] = e;
return true;
}

此方法中有两个操作,一个是数组容量检查,另外就是将元素放入数据中。我们先看第二个简单的开始分析,当多个线程执行顺序如下所示的时候,会出现最终数据元素个数小于期望值。

ArrayList源码和多线程安全问题分析

按照此顺序执行完之后,我们可以看到,elementdata[n]的只被设置了两次,第二个线程设置的值将前一个覆盖,最后size=n+1。下面使用代码进行验证此问题。

1.3 代码验证

首先先看下以下代码,开启1000个线程,同时调用arraylist的add方法,每个线程向arraylist中添加100个数字,如果程序正常执行的情况下应该是输出:

list size is :10000

代码如下:

private static list<integer> list = new arraylist<integer>();
private static executorservice executorservice = executors.newfixedthreadpool(1000);
private static class increasetask extends thread{
@override
public void run() {
system.out.println("threadid:" + thread.currentthread().getid() + " start!");
for(int i =0; i < 100; i++){
list.add(i);
}
system.out.println("threadid:" + thread.currentthread().getid() + " finished!");
}
}
public static void main(string[] args){
for(int i=0; i < 1000; i++){
executorservice.submit(new increasetask());
}
executorservice.shutdown();
while (!executorservice.isterminated()){
try {
thread.sleep(1000*10);
}catch (interruptedexception e){
e.printstacktrace();
}
}
system.out.println("all task finished!");
system.out.println("list size is :" + list.size());
}

当执行此main方法后,输出如下:

ArrayList源码和多线程安全问题分析

从以上执行结果来看,最后输出的结果会小于我们的期望值。即当多线程调用add方法的时候会出现元素覆盖的问题。

1.4 数组容量检测的并发问题

在add方法源码中,我们看到在每次添加元素之前都会有一次数组容量的检测,add中调用此方法的源码如下:

ensurecapacityinternal(size + 1);

容量检测的相关源码如下:

private void ensurecapacityinternal(int mincapacity) {
if (elementdata == defaultcapacity_empty_elementdata) {
mincapacity = math.max(default_capacity, mincapacity);
}
ensureexplicitcapacity(mincapacity);
}
private void ensureexplicitcapacity(int mincapacity) {
modcount++;
// overflow-conscious code
if (mincapacity - elementdata.length > 0)
grow(mincapacity);
}

容量检测的流程图如下所示:

ArrayList源码和多线程安全问题分析

我们以两个线程执行add操作来分析扩充容量可能会出现的并发问题:
当我们新建一个arraylist时候,此时内部数组容器的容量为默认容量10,当我们用两个线程同时添加第10个元素的时候,如果出现以下执行顺序,可能会抛出java.lang.arrayindexoutofboundsexception异常。

ArrayList源码和多线程安全问题分析

第二个线程往数组中添加数据的时候由于数组容量为10,而此操作往index为10的位置设置元素值,因此会抛出数组越界异常。

1.5 代码验证数组容量检测的并发问题

使用如下代码:

private static list<integer> list = new arraylist<integer>(3);
private static executorservice executorservice = executors.newfixedthreadpool(10000);
private static class increasetask extends thread{
@override
public void run() {
system.out.println("threadid:" + thread.currentthread().getid() + " start!");
for(int i =0; i < 1000000; i++){
list.add(i);
}
system.out.println("threadid:" + thread.currentthread().getid() + " finished!");
}
}
public static void main(string[] args){
new increasetask().start();
new increasetask().start();
}

执行main方法后,我们可以看到控制台输出如下:

ArrayList源码和多线程安全问题分析

1.6 arraylist中其他方法说明

arraylist中其他包含对共享变量操作的方法同样会有并发安全问题,只需要按照以上的分析方法分析即可。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。