思路

java.util.ArrayList
中的 add(E e)
方法里有 size++
的操作,而 size++
并不是原子操作,所以如果在 size++
的过程中,其他线程被调度,并且也去操作同一个 ArrayList
,那么 size
的值就可能不准确了。
实战
有了这样的思路,我们就可以让一些线程在同一个 ArrayList
上调用 add(E e)
方法。我写了两段示例代码来进行验证。
首先是让 100 个线程对同一个 ArrayList
执行 add(E e)
方法。
并发地对 ArrayList
进行添加元素的操作
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Main {
public static void main(String[] args) throws Exception {
// 让 cnt 个线程并发地对 list 进行添加元素的操作
final int cnt = 100;
CountDownLatch countDownLatch = new CountDownLatch(cnt);
ExecutorService executorService = Executors.newCachedThreadPool();
final int n = 100;
// 令 initialCapacity=cnt * n, 容量肯定够大了, 程序运行过程中就不会出现扩容
List<Integer> list = new ArrayList<>(cnt * n);
Runnable runnable = () -> {
for (int i = 0; i < n; i++) {
// 添加元素
list.add(i);
// 尝试让其他线程执行, 让问题更容易暴露出来
Thread.yield();
}
countDownLatch.countDown();
};
for (int i = 0; i < cnt; i++) {
executorService.execute(runnable);
}
executorService.shutdown();
countDownLatch.await();
System.out.println(list.size());
}
}
一次运行结果如下

并发地对 Vector
进行添加元素的操作
java.util.Vector
是线程安全的,所以如果我们用一些线程并发地对 Vector
进行添加元素的操作,最终的 size
应该不会有问题。
将上面程序里的 ArrayList
改为 Vector
,程序会变为
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Main {
public static void main(String[] args) throws Exception {
// 让 cnt 个线程并发地对 list 进行添加元素的操作
final int cnt = 100;
CountDownLatch countDownLatch = new CountDownLatch(cnt);
ExecutorService executorService = Executors.newCachedThreadPool();
final int n = 100;
// 令 initialCapacity=cnt * n, 容量肯定够大了, 程序运行过程中就不会出现扩容
List<Integer> list = new Vector<>(cnt * n);
Runnable runnable = () -> {
for (int i = 0; i < n; i++) {
// 添加元素
list.add(i);
// 尝试让其他线程执行, 让问题更容易暴露出来
Thread.yield();
}
countDownLatch.countDown();
};
for (int i = 0; i < cnt; i++) {
executorService.execute(runnable);
}
executorService.shutdown();
countDownLatch.await();
System.out.println(list.size());
}
}
一次运行结果如下

为什么并发地对
Vector
执行 add(E e)
操作就没有出现 size 不对的问题呢?
上图是
Vector
源码中的 add(E e)
部分,可见 add(E e)
是一个同步方法,所以 Vector
中的add(E e)
方法相当于一个原子操作。对同一个 Vector
而言,只有一个线程执行完 add(E e)
方法后,其他线程才有可能再次执行 add(E e)
方法。这样 elementCount 的值就不会在 add(E e)
执行期间被弄错。Vector
的 size()
方法返回的就是 elementCount
的值
网友评论