java已经有很多有用的模块了。但是很多时候,一个类只能支持一部分操作,需要我们再不破坏线程安全的前提下,添加新操作,比如我们需要一个线程安全的list,给他加这么一个方法:缺少即加入
有这么几种实现方式
(1) 直接改源码
(2)扩展类,继承原有的vector
(3)扩展功能类:就是helper,原有模块作为其中的关键成员
下面是一个错误的实现案例:
Java并发编程实战这本书里提到了使用Collections.synchronizedList可以创建线程安全的容器,同时给了一个没有正确使用该容器的反例ListHelper,这个反例看起来实现了同步,然而由于锁不一致导致它并不是一个线程安全的类。代码如下:
class ListHelper <E> {
public List<E> list = Collections.synchronizedList(new ArrayList<E>());
public synchronized boolean putIfAbsent(E x) {
boolean absent = ! list.contains(x);
if (absent)
list.add(x);
return absent;
}
}
我们知道,Collections.synchronizedList 本身是一个线程安全的,他的contains方法使用mutex作为锁,这里的mutex初始化时,其实就是list本身
public boolean contains(Object o) {
synchronized (mutex) {return c.contains(o);}
但是这里Collections.synchronizedList加的戏,用的是该方法的调用者ListHelper对象, 因此,这个helper两个方法的锁不一样(一个是list,一个是listhelper),不能保证这两个方法同时使用时的线程安全
比如下面的代码,我再执行putifAbsent的时候,其他线程改动了,导致最终size不对
public class Test {
public static void main(String[] args) throws Exception {
ListHelper<Integer> helper = new ListHelper<>();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
Test.t1(helper);
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
Test.t2(helper);
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(helper.list.size());
}
private static void t1(ListHelper<Integer> helper) {
for (int i = 0; i < 100000; i++)
helper.putIfAbsent(i);
}
private static void t2(ListHelper<Integer> helper) {
for (int i = 0; i < 100000; i++)
synchronized (helper.list) { // correct way to synchronize
if (!helper.list.contains(i))
helper.list.add(i);
}
}
}
最终输出的结果大于10000
当然这个案例关注的问题是:单纯来看putIfAbsent 这个方法,它本身一定是线程安全的,但由于该方法使用的锁不正确,导致了putIfAbsent所属的类却不是线程安全的。如果你开启100000个线程交替执行putIfAbsent方法,那么始终输出100000这个结果。
同时案例引发的思考是:如果引用外部线程安全的容器,那么必须保证这个容器和类方法使用同一个锁,这个类才是线程安全的类。
所以,ListHelper要改造成线程安全的类,必须使用和list一致的锁,即使用如下的同步代码块的方式:
class ListHelper <E> {
public List<E> list = Collections.synchronizedList(new ArrayList<E>());
public boolean putIfAbsent(E x) {
synchronized (list) {
boolean absent = ! list.contains(x);
if (absent)
list.add(x);
return absent;
}
}
}