2.4 List集合线程不安全
之前我们在使用ArrayList的时候,觉得它并没有不安全,是因为我们是在单线程环境下使用的,如果在多线程环境下,那么ArrayList就不够安全了!
2.4.1 测试List集合是否安全
1.单线程下测试ArrayList集合
package java.util;
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
public void add(int index, E e) {
rangeCheckForAdd(index);
checkForComodification();
parent.add(parentOffset + index, e);
this.modCount = parent.modCount;
this.size++;
}
}
package com.kuang.unsafe;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
public class ListTest {
public static void main(String[] args) {
List<String> list = Arrays.asList("1","2","3");
list.forEach(System.out::println);
}
结果:执行成功,没有出现异常!
2.多线程下测试ArrayList集合
package java.util;
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
public void add(int index, E e) {
rangeCheckForAdd(index);
checkForComodification();
parent.add(parentOffset + index, e);
this.modCount = parent.modCount;
this.size++;
}
}
package com.kuang.unsafe;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
public class ListTest {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 1; i <= 10; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
结果:出现异常报错:java.util.ConcurrentModificationException(并发修改异常)!
测试结论:并发情况下,ArrayList线程不安全
2.4.2 解决List线程不安全问题
1.将ArrayList替换为Vector集合
2.使用Collections集合工具类的synchronizedList方法
3.使用CopyOnWriteArrayList(写时复制数组集合)
1.使用Vector集合
package java.util;
public class Vector<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
package com.kuang.unsafe;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
public class ListTest {
public static void main(String[] args) {
List<String> list = new Vector<>();
for (int i = 1; i <= 10; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
结果:执行成功,没有出现异常!
2.使用Collections工具类的synchronizedList方法
Collections工具类:
package java.util;
public class Collections {
private Collections() {
}
public static <T> List<T> synchronizedList(List<T> list) {
return (list instanceof RandomAccess ?
new SynchronizedRandomAccessList<>(list) :
new SynchronizedList<>(list));
}
}
RandomAccess接口:
package java.util;
public interface RandomAccess {
}
- RandomAccess接口是一个标志接口,实现了该接口的List集合,可以支持快速随机访问;
- 如果实现了该接口的List集合,使用for循环的方式获取数据将优于迭代器获取
for (int i=0, n=list.size(); i < n; i++)
list.get(i);
for (Iterator i=list.iterator(); i.hasNext(); )
i.next();
package com.kuang.unsafe;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
public class ListTest {
public static void main(String[] args) {
List<String> list = Collections.synchronizedList(new ArrayList<>());
List<String> list = new CopyOnWriteArrayList<>();
for (int i = 1; i <= 10; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
结果:执行成功,没有出现异常!
3.CopyOnWriteArrayList简单了解
CopyOnWriteArrayList:顾名思义,写入时进行复制
什么是写入时复制?
写入时复制(全称为Copy On Write,简称为COW),是计算机程序设计领域的一种优化策略;
其核心思想是:如果有多个调用者同时请求相同资源(如内存或磁盘上的数据存储),它们会共同获取相同的指针指向相同的资源,直到某个调用者试图修改资源的内容时,系统才会真正复制一份专用副本(private copy)给该调用者,而其他调用者所见到的最初的资源仍然保持不变。这过程对其他的调用者都是透明的。
此作法主要的优点是如果调用者没有修改该资源,就不会有副本被创建,因此多个调用者只是读取操作时可以共享同一份资源。
简而言之,就是读操作直接在正本上进行,一旦有写操作,就复制一份副本出来,并在副本上做修改
4.使用CopyOnWriteArrayList集合
package java.util.concurrent;
public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
private static final long serialVersionUID = 8673264195747942595L;
final transient ReentrantLock lock = new ReentrantLock();
private transient volatile Object[] array;
final Object[] getArray() {
return array;
}
final void setArray(Object[] a) {
array = a;
}
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
...(中间省略部分代码)...
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();
}
}
...(后面省略部分代码)...
}
transient关键字使用总结:
1、transient关键字只能修饰变量,而不能修饰方法和类。注意,本地变量是不能被transient关键字修饰的。 2、被transient关键字修饰的变量不再能被序列化,一个静态变量不管是否被transient修饰,均不能被序列化。 3、一旦变量被transient修饰,变量将不再是对象持久化的一部分,该变量内容在序列化后无法获得访问。也可以认为在将持久化的对象反序列化后,被transient修饰的变量将按照普通类成员变量一样被初始化。
package com.kuang.unsafe;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
public class ListTest {
public static void main(String[] args) {
List<String> list = new CopyOnWriteArrayList<>();
for (int i = 1; i <= 10; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
结果:执行成功,没有出现异常!
到这里,今天的有关List集合线程安全的学习就结束了,欢迎大家学习和讨论!
参考视频链接:https://www.bilibili.com/video/BV1B7411L7tE (B站UP主遇见狂神说的JUC并发编程基础)
参考博客链接:
https://baijiahao.baidu.com/s?id=1636557218432721275&wfr=spider&for=pc (Java中的关键字transient,这篇文章你再也不愁了)
https://www.cnblogs.com/liuling/archive/2013/05/05/transient.html (Java transient关键字使用小结)
https://blog.csdn.net/nazeniwaresakini/article/details/104473981 (写时复制(COW)技术与其在Linux、Java中的应用)
|