1. 发现问题
List<Integer> list = new ArrayList<>();
new Thread(() -> {
for (int i = 0; i < 10000; i++) {
list.add(1);
}
},"A").start();
new Thread(() -> {
for (int i = 0; i < 10000; i++) {
list.add(1);
}
},"B").start();
new Thread(() -> {
for (int i = 0; i < 10000; i++) {
list.add(1);
}
},"C").start();
try {
TimeUnit.SECONDS.sleep(3);
}catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("list.size() = " + list.size());
开启三个线程,每个线程向ArrayList 中插入1w 条数据。
之后等待三秒,等到每个线程都执行完毕时再查看ArrayList 中的元素个数。
运行结果:
- 问题一:
ArrayIndexOutOfBoundsException
public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
在多线程环境中时,多个线程同时进入add() 方法,同时检查容量,例如当前容量为5 ,而已占用4 。
三个线程同时检查,都发现还有容量,则都同时添加元素。
由此导致ArrayIndexOutOfBoundsException 。
从运行结果可以看出,最终list.size() 只有18935 <= 30000 。我们希望能够插入30000 个元素,可是实际上只插入了<= 30000 个元素。
还是从源码入手:
public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
试想一下,如果多个线程同时向size 位插入元素,且都没有来得及size++ ,那么导致的结果就是
多个元素被插入在了同一个位置,相互抵消。
2. 解决问题(一)Vector
早期,IT 前人为了解决List 在并发时出现的问题,引入了Vector 实现类。
Vetor 的实现方式与ArrayList 大同小异,它的底层也是一个数组,在添加时自增长。
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
与ArrayList 不同的是,它的add() 方法带有synchronized 关键字。
这表明当线程调用该方法时,会自动占用锁,直到这个线程的任务完成,期间不会放弃该锁。
而且当线程占有该锁时,别的线程无法进入Vetor 类调用带有synchronized 关键字的方法。
这很好的避免了多线程竞争的现象,从而保证了并发安全。
试一试?
Vector<Integer> list = new Vector<>();
new Thread(() -> {
for (int i = 0; i < 10000; i++) {
list.add(1);
}
},"A").start();
new Thread(() -> {
for (int i = 0; i < 10000; i++) {
list.add(1);
}
},"B").start();
new Thread(() -> {
for (int i = 0; i < 10000; i++) {
list.add(1);
}
},"C").start();
try {
TimeUnit.SECONDS.sleep(3);
}catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("list.size() = " + list.size());
3.解决问题(二)Collections.synchronizedList(List<T> list)
废话不多说,直接上源码。
List<Object> list = Collections.synchronizedList(new ArrayList<>());
public static <T> List<T> synchronizedList(List<T> list) {
return (list instanceof RandomAccess ?
new SynchronizedRandomAccessList<>(list) :
new SynchronizedList<>(list));
}
SynchronizedList(List<E> list) {
super(list);
this.list = list;
}
SynchronizedCollection(Collection<E> c) {
this.c = Objects.requireNonNull(c);
mutex = this;
}
public static <T> T requireNonNull(T obj) {
if (obj == null)
throw new NullPointerException();
return obj;
}
Collections.synchronizedList() 方法会返回一个SynchronizedList 类的实例,其中包含了调用该方法时传入的集合,在构造期间,将SynchronizedCollection 作为互斥锁。
此时,当我们再调用add() 方法:
public boolean add(E e) {
synchronized (mutex) {
return c.add(e);
}
}
这是,当调用add() 方法,SynchronizedCollection 会锁住自己,从而保证线程安全。
当有线程正在使用mutex 互斥锁时,其他变量无法占有该锁。
4. 手写实现一个简易版SynchronizedCollection
package top.ptcc9;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
public class MySynchronizedArrayList<E> implements List<E> {
private Collection<E> list = null;
private final Object object = new Object();
public MySynchronizedArrayList(Collection<E> t) {
this.list = t;
}
@Override
public int size() {
return list.size();
}
@Override
public boolean add(E e) {
synchronized (object) {
list.add(e);
}
return true;
}
}
List<Integer> list = new MySynchronizedArrayList<>(new ArrayList<>());
new Thread(() -> {
for (int i = 0; i < 10000; i++) {
list.add(1);
}
},"A").start();
new Thread(() -> {
for (int i = 0; i < 10000; i++) {
list.add(1);
}
},"B").start();
new Thread(() -> {
for (int i = 0; i < 10000; i++) {
list.add(1);
}
},"C").start();
try {
TimeUnit.SECONDS.sleep(3);
}catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("list.size() = " + list.size());
未完待更新呢
|