ListHelper为什么不是线程安全的

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;

        }

    }

}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值