ArrayList不是线程安全的
怎么证明呢?
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
new Thread(()->{
try {
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
}finally {
}
},String.valueOf(i+1)).start();
}
}
运行结果:
List<String> list = new ArrayList<>();
list.add("AA");
list.add("BB");
list.add("CC");
for (String s : list) {
System.out.println(s);
list.remove(s);
}
两个示例都抛出了并发修改异常。第二个示例foreach 会产生一个iterator进行迭代。
解决方法
1、synchronized
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
new Thread(()->{
synchronized(list){
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
}
},String.valueOf(i+1)).start();
}
}
2、Reentrantlock
public static void main(String[] args) {
List<String> list = new ArrayList<>();
Lock lock = new ReentrantLock();
for (int i = 0; i < 10; i++) {
new Thread(()->{
lock.lock();
try {
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
}finally {
lock.unlock();
}
},String.valueOf(i+1)).start();
}
}
3、collections
看对应api即可
4、copyOnWriteAraaylist
public static void main(String[] args) {
List<String> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 10; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
},String.valueOf(i+1)).start();
}
}
CopyOnWriteArrayList原理
CopyOnWriteArrayList遵循的是读写分离的思想。读取数据的时候,可以直接读取。但是写数据在多线程环境下,可能出现线程安全问题。
我们可以看下对应源码
add()
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
步骤
1、add对象时,获取可重入锁,只有线程获取到该锁,才能进行写操作
2、获取原对象数组,通过Arrays拷贝生成一个原数组的副本。将add的对象放入数组末尾,将原数组引用指向该进行了写操作的数组副本(这一部分保证原子性)
3、操作完后释放锁,其它线程可访问该方法进行写操作
get()
// Positional Access Operations
@SuppressWarnings("unchecked")
private E get(Object[] a, int index) {
return (E) a[index];
}
/**
* {@inheritDoc}
*
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E get(int index) {
return get(getArray(), index);
}
可以看到get方法并没有加锁,因此线程无需互斥访问get方法,提高了并发处理速度