我正在学习Java多线程。我写了一小段代码并导致一些我无法理解的输出。请帮忙解释一下。 发布以下代码。
package com.java.learn;
import java.util.ArrayList;
import java.util.List;
public class ListTestWithMultiThread {
static final List<Integer> list = new ArrayList<Integer>();
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
list.add(Integer.valueOf(i));
}
System.out.println("List size at thread 0 : " + list.size());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 101; i <= 200; i++) {
list.add(Integer.valueOf(i));
}
System.out.println("List size at thread 1 : " + list.size());
}
}).start();
}
}
各种运行中的一些o / p: 线程0:134的列表大小 线程1:200的列表大小
Exception in thread "Thread-1" List size at thread 0 : 101
java.lang.ArrayIndexOutOfBoundsException: 17
at java.util.ArrayList.add(Unknown Source)
at com.java.learn.ListTestWithMultiThread$2.run(ListTestWithMultiThread.java:25)
at java.lang.Thread.run(Unknown Source)
List size at thread 0 : 106
Exception in thread "Thread-1" java.lang.ArrayIndexOutOfBoundsException: 58
at java.util.ArrayList.add(Unknown Source)
at com.java.learn.ListTestWithMultiThread$2.run(ListTestWithMultiThread.java:25)
at java.lang.Thread.run(Unknown Source)
答案 0 :(得分:2)
您正在访问并非专为并发访问而设计的数据结构(synchronized
),而不保护它(例如,通过List<Integer> list = Collections.synchronizedList(new ArrayList<Integer>());
)。这最终会破坏数据结构的内部结构,导致奇怪的行为,例如你得到的异常。
以下是两种方法:
synchronized
使用synchronized(list) {
list.add(Integer.valueOf(i));
}
保护列表:
ArrayList
修改:由于您的要求,ArrayList
可能会被破坏。
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
...
elementData = Arrays.copyOf(elementData, newCapacity);
由数组支持,该数组在列表增长时必须调整大小。在此处调整大小意味着分配一个新的更大的数组,并将旧的数组的内容复制到新的数组中。以下是the code的部分内容:
elementData
现在想象如下:线程A开始调整数组大小,它计算新容量并开始复制第4行中的数据。但在它将新数组的引用复制到elementData
之前,它会被停止无论什么原因(这种情况一直发生)。现在,线程B开始调整数组的大小并完成。然后它会在列表中插入更多值并再次调整数组大小并完成。线程B现在假定列表足够大以容纳新值,但在插入值之前,线程A唤醒并覆盖ArrayIndexOutOfBoundsException
并引用它创建的较小数组。线程B现在尝试将值插入较小的数组并获得var day = DateTime.Now.Day;
var month = DateTime.Now.Month;
var query = (from emp in db.Employees
where emp.EMP_DOB.HasValue == true
&& emp.EMP_DOB.Value.Day >= day && emp.EMP_DOB.Value.Month == month
select new Birthday
{
Name = emp.EMP_USERNAME,
date = emp.EMP_DOB.Value
}).ToList();
return query;
。这一切都不太可能,但它可能会发生,如你所见。
答案 1 :(得分:0)
您正在从两个不同的线程访问list
变量而没有任何同步(锁定)。这将导致未定义的行为。
您可能想尝试使用以下内容替换列表声明:
static final List list = Collections.synchronizedList(new ArrayList());
另一种选择是使用Vector
代替ArrayList
。 Vector
是实现List
接口的同步集合。
答案 2 :(得分:0)
ArrayList
不是线程安全的。 ArrayList
实施已备份
通过数组。有关联ArrayList
的大小变量。每当我们
将任何元素添加到ArrayList
它首先确保它的容量然后
向数组添加元素。可以看到数组列表的状态
因为您共享非线程安全,所以每个线程都是未定义的
两个线程之间的实现。
如果您先启动任何线程,请不要认为它会完成它 任务第一。没有预测的行为。
答案 3 :(得分:0)
我认为错误是outofbound而不是java.util.ConcurrentModificationException 要理解输出,首先应该了解内部工作的arraylist。当你添加arraylist时,内部发生了什么。 ArrayList的大小如何动态增长?
在add(Object)中,您将找到以下代码
public boolean add(E e)
{
ensureCapacity(size+1);
elementData[size++] = e;
return true;
}
从上面的代码中注意到的重点是我们在添加元素之前检查ArrayList的容量。 ensureCapacity()确定占用元素的当前大小以及数组的最大大小。如果填充元素的大小(包括要添加到ArrayList类的新元素)大于数组的最大大小,则增加数组的大小。但是数组的大小不能动态增加。所以内部发生的是新的数据是用容量创建的。
所以在你的情况下你的 线程1和线程2尝试并成功地同时添加元素而没有同步错误。直到Arraylist已满,现在两个线程都会创建2个不同的新数组。其中一个线程赢得更改对其新创建的数组的引用,假设它的线程1.现在线程2尝试复制新引用的数组的所有元素,不幸的是他不知道数组的大小更改因此重新计算数组的大小并尝试添加新元素at数组顶部不存在。因此抛出异常超出界限。
如果我理解错误,请纠正我。