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

时间:2018-07-28 17:04:17

标签: java multithreading thread-safety

我最近在读一本书《 Java Concurrency in Practice 2nd》,作者提到如果我们使用Collections.synchronizedList创建一个安全的线程列表,那么我们必须确保使用的锁与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中的一个对象锁定,但是list.contains没有将此对象用作锁,因为有两个锁,所以在多线程处理下它是不安全的。但是我的问题是如何证明它不是线程安全的。你有什么想法吗?

2 个答案:

答案 0 :(得分:9)

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

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

  • t1使用班级的putIfAbsent方法
  • t2使用synchronized块来正确锁定synchronizedList用于控制访问的“互斥”对象,即包装器列表本身。

由于这两种方法都试图添加 same 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;
    }
}

答案 1 :(得分:1)

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

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]