为了账号安全,请及时绑定邮箱和手机立即绑定

如何证明类 ListHelper<E> 不是线程安全的

如何证明类 ListHelper<E> 不是线程安全的

湖上湖 2021-06-08 13:12:46
最近在看《Java Concurrency in Practice 2nd》一书,作者提到如果我们使用Collections.synchronizedList来创建一个安全线程List,那么我们必须确保我们使用的是同一个锁,它是来自SynchronizedCollection的一个Object。下面的代码来自书中:public 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;    }}在这个类中,方法putIfAbsent已经被ListHelper中的一个Object锁住了,但是list.contains并没有使用这个Object作为锁,有两个锁,所以在多线程下是不安全的。但我的问题是如何证明它不是线程安全的。你有什么想法?
查看完整描述

2 回答

?
隔江千里

TA贡献1906条经验 获得超10个赞

以下代码证明您的类不是线程安全的。

它在两个不同的线程中将 100000 个数字添加到列表中:

  • t1使用putIfAbsent你的类的方法

  • t2使用synchronized块正确锁定synchronizedList用于控制访问的相同“互斥”对象,即包装器列表本身。

由于两种方法都试图添加相同的100000 个对象,结果应该是 100000 个对象的列表,即代码应该100000在最后打印。

有时它确实如此,当我运行它时,但大多数时候它比那个高一点,例如100075,从而证明您putIfAbsent的不是线程安全的。

import java.util.ArrayList;

import java.util.Collections;

import java.util.List;


public class Test {

    public static void main(String[] args) throws Exception {

        ListHelper<Integer> helper = new ListHelper<>();

        Thread t1 = new Thread(() -> Test.t1(helper));

        Thread t2 = new Thread(() -> Test.t2(helper));

        t1.start();

        t2.start();

        t1.join();

        t2.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);

            }

    }

}

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;

    }

}


查看完整回答
反对 回复 2021-06-17
?
Cats萌萌

TA贡献1805条经验 获得超9个赞

通过确保在操作之间运行第二个线程,您可以表明您添加的第一个元素可能存在问题。


import java.util.ArrayList;

import java.util.Collections;

import java.util.List;


public class ListHelper<E> {

    public final List<E> list = Collections.synchronizedList(new ArrayList<E>());


    public synchronized boolean putIfAbsent(E x) {

        boolean absent = !list.contains(x);

        runInANotherThread(() -> list.add(x));

        if (absent)

            list.add(x);

        return absent;

    }


    public static void runInANotherThread(Runnable run) {

        Thread t = new Thread(run);

        t.start();

        try {

            t.join(1000);

        } catch (InterruptedException ignored) {

        }

    }


    public static void main(String[] args) {

        ListHelper<Integer> list = new ListHelper<>();

        list.putIfAbsent(1);

        System.out.println(list.list);

    }

}

印刷


[1, 1]


查看完整回答
反对 回复 2021-06-17
  • 2 回答
  • 0 关注
  • 127 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信