Java 中 ArrayList 和 LinkedList 都不是线程安全的,但可以通过 java.util.Collections.synchronizedList(List list) 方法,获取一个线程安全的 List 实例对象。
将非线程安全 List 对象,封装成一个线程安全的 List 对象,处理 List 上的并发性问题。类似一个工具类,减少开发人员的重复性工作。
1.定义个 Obj 类,内部使用一个 Collections.synchronizedList(List) 构造一个线程安全的 List 对象。
static class Obj {
private List synchronizedList = Collections.synchronizedList(new ArrayList<>());
public void add(String s) {
synchronizedList.add(s);
}
public void remove(String s) {
synchronizedList.remove(s);
}
public int size() {
return synchronizedList.size();
}
}
2.创建一个 Obj 对象 obj, 两个线程 t1 和 t2,并在 t1 和 t2 中访问 obj 对象,并发的向 obj 对象中添加元素。每个线程循环执行 100 次添加操作。
public class Test {
public static void main(String[] args) {
final Obj obj = new Obj();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
String val = null;
for (int i = 0; i < 100; i++) {
val = "t1: " + i;
obj.add(String.valueOf(val));
System.out.println("add: " + val);
sleep(50);
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
String val = null;
for (int i = 0; i < 100; i++) {
val = "t2: " + (100 + i);
obj.add(String.valueOf(val));
System.out.println("add: " + val);
sleep(50);
}
}
});
t1.start();
t2.start();
try {
t1.join(); //等待线程 t1 结束
t2.join(); //等待线程 t2 结束
} catch (Exception e) {
}
String name = Thread.currentThread().getName();
System.out.println("Thread name: " + name + ", Obj size: " + obj.size());
}
static void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (Exception e) {
System.out.println("Exception: " + e.getMessage());
}
}
}
在步骤 2 中,最终的输出结果为,
...
...
add: t1: 95
add: t2: 194
add: t2: 195
add: t1: 96
add: t2: 196
add: t1: 97
add: t1: 98
add: t2: 197
add: t2: 198
add: t1: 99
add: t2: 199
Thread name: main, Obj size: 200
可以看到,最终 obj 的 size 为预期的 200
如果将 Obj 类中的 List 改为普通的 ArrayList,如下,
static class Obj {
private List list = new ArrayList<>();
public void add(String s) {
list.add(s);
}
public void remove(String s) {
list.remove(s);
}
public int size() {
return list.size();
}
}
使用同样的测试用例,最终的输出结果为,
...
...
add: t2: 193
add: t1: 94
add: t2: 194
add: t1: 95
add: t2: 195
add: t1: 96
add: t2: 196
add: t1: 97
add: t2: 197
add: t1: 98
add: t2: 198
add: t1: 99
add: t2: 199
Thread name: main, Obj size: 185
从上面的结果可知,最终 obj 的 size 只有 185,跟我们预期的 200 不一样。表示出现了并发性问题。
正常情况下,Collections.synchronizedList(List list) 返回的是一个 SynchronizedList 的对象,这个对象以组合的方式将对 List 的接口方法操作,委托给传入的 list 对象,并且对所有的接口方法对象加锁,得到并发安全性。
Collections.synchronizedList(List list) 方法源码
public static List synchronizedList(List list) {
return (list instanceof RandomAccess ?
new SynchronizedRandomAccessList<>(list) :
new SynchronizedList<>(list));
}
当传入的 list 是 ArrayList 时,返回 SynchronizedRandomAccessList 对象;传入 LinkedList 时,返回 SynchronizedList 对象。
再来看看 SynchronizedList 源码,
static class SynchronizedList
extends SynchronizedCollection
implements List {
...
...
final List list;
SynchronizedList(List list) {
super(list);
this.list = list;
}
SynchronizedList(List list, Object mutex) {
super(list, mutex);
this.list = list;
}
...
...
public E get(int index) {
synchronized (mutex) {return list.get(index);}
}
public E set(int index, E element) {
synchronized (mutex) {return list.set(index, element);}
}
public void add(int index, E element) {
synchronized (mutex) {list.add(index, element);}
}
public E remove(int index) {
synchronized (mutex) {return list.remove(index);}
}
...
...
}
可以看到,SynchronizedList 的实现里,get, set, add 等操作都加了 mutex 对象锁,再将操作委托给最初传入的 list。
这就是以组合的方式,将非线程安全的对象,封装成线程安全对象,而实际的操作都是在原非线程安全对象上进行,只是在操作前给加了同步锁。
由于有很多业务场景下都有这种需求,所以 Java 类库中封装了这个工具类,给需要的模块使用。