【问题标题】:How to make this function thread safe?如何使这个函数线程安全?
【发布时间】:2019-03-29 13:10:45
【问题描述】:
public class Sol {



    static Map<Integer, List<String>> emap;
    static List<Integer> sortSalaries(List<List<String>> workers) {
        List<Integer> res = new ArrayList<Integer>();
        emap = new HashMap<>();
        for (List<String> e: workers)
            emap.put(Integer.parseInt(e.get(0)), e);

        for(List<String> worker: workers )
        {
         //accessing workers
         .....
        }


        Collections.sort(res);

        return res;


    }

    public static int dfs(int eid) {
        List<String> employee = emap.get(eid);
        int salary=0;
        String ans = employee.get(3);
        for (int i=0;i<ans.length();i=i+2)

        {
            // accesing emap
          ......
        }

        return salary;
    }


}

我是否必须使用同步关键字才能使其线程安全。如果方法是同步的,我是否必须使用 Vector 和 Hashtable。

或者,如果我使用 Vector 和 Hashtable,将 emap 变量移动到 sortSalaries() 并将其传递给 dfs()。在这种情况下如果我不使用同步关键字可以吗..

【问题讨论】:

  • 这个是线程的怎么办?
  • 我看不到任何run 方法?这实际上是一个线程吗?
  • 这些函数是从线程中调用的......所以我想确保返回正确的数据
  • 你为什么使用static方法?创建此类的单个实例不适合您?
  • @mzz:首先,你明白为什么这些方法如果从多个线程调用不是线程安全的吗?

标签: java


【解决方案1】:

我在评论中问了你一个问题 - 你明白为什么如果从多个线程调用这些方法不是线程安全的吗?你给我指出了一个链接,但没有说明你是否真的理解它与否,为什么您认为您的课程不是线程安全的,所以我提供了一些背景知识,而不是直接回答问题。

简短的讨论

当您开始在运行/调用线程之间共享数据时,任何类或其方法都可能变得不安全。默认情况下,您的类是线程安全的 - 如果线程之间没有共享数据,那么使您的类线程安全的最简单方法是在线程之间停止共享数据,在您的情况下,它将被删除- emap(因为它是一个类状态并在方法中使用)和List&lt;List&lt;String&gt;&gt; workers(这是我不确定的,因为它是从调用者传递的引用,并且不同的方法调用将在同一个实例上工作或可能不同实例被传递给这个方法)并用方法局部变量替换它们。

方法局部变量默认是线程安全的,因为每次调用都会创建和销毁新实例。

如果您不能这样做或不可行,请按照 oleg.cherednik 的回答在块级别或方法级别同步变量 - emap。请记住,在 Java 中同步有多种方法,synchronized 关键字是最简单的。

现在对于方法参数 - List&lt;List&lt;String&gt;&gt; workersint eid ,不需要对 eid 进行同步,因为您只是在读取它而不是更新它并且由于类型是原始的,它也不是通过引用传递而是通过值传递。

如果您将相同的列表实例传递给来自不同线程的此方法的调用,则需要同步访问List&lt;List&lt;String&gt;&gt; workers。请参阅Gray's Answer - Here 并在 oleg.cherednik 的回答中遗漏了这一点。您最好判断此参考是否需要同步。

很容易假设List 迭代是线程安全的(因为您没有更新列表),但这可能并不总是正确的。 Refer this question and all answers 进行详细讨论。

所以总结是这样的——你开始为你的类实现线程安全,首先分析一些对象是否在线程之间共享。如果对象是共享的,则对这些对象的读/写需要同步(使其具有原子性并提供这些对象还不是线程安全的)。如果没有共享对象 - 它已经是线程安全的。此外,尝试使用已经是线程安全的数据结构来创建类,这样你要做的工作就会更少。

java.lang.NullPointerException ( NPE ) oleg.cherednik 的回答也是如此。

【讨论】:

  • 尽管一个引用被传递给 List> 工人,工人仍然是一个局部变量(方法参数是局部变量),它被传递给工人的新副本,对吗??所以工人不需要同步,对吧?
  • 不,它不是方法局部变量,这就是我为什么指出格雷对其他问题的回答。为此需要同步。
  • 当线程 A 调用 sortSalaries 并传递工人的引用时,线程 A 是否可以在执行 sortSalaries 时更改工人的内容?没有权利 ? sortSalaries 必须完成线程 A 的执行才能恢复执行?
  • @mzz:忘记修改,迭代本身可能不是线程安全的。这完全取决于您如何在此方法中进行同步。这只是需要考虑的一点(不仅仅是不加考虑就留下),可能不适用于您的情况。所以我想说,显示你最终更改的代码,然后我们可以讨论。
【解决方案2】:
  1. 保护emap 免受外部访问
  2. 初始化emap 以排除NPE

例子:

public final class Sol {

    private static final Map<Integer, List<String>> emap = new HashMap<>();

    static List<Integer> sortSalaries(List<List<String>> workers) {
        synchronized (Foo.class) {
            for (List<String> e : workers)
                emap.put(Integer.parseInt(e.get(0)), e);
        }

        // do smth, not access emap
    }

    public static synchronized int dfs(int eid) {
        // do smth with accessing emap
    }
}

sortSalaries 中,您可以使用for loop 最小化synchoronized 块。在dfs 中,您在方法的不同位置访问emap,因此您必须同步整个方法。

在这里使用ConcurrentHashMapVector 没有帮助,因为在get/set 元素到集合之间,它们可以被更改,这对于dfs 方法是不行的:它应该在emap调用。

【讨论】:

  • 我也在 sortSalaries 中使用 res 数组列表,因此必须正确同步整个函数。你能说出为什么不需要更清楚地使用vector或hashtable吗
  • @mzz : res 是一个方法局部变量,为每个方法调用创建一个新实例(不共享实例),因此不需要同步。
猜你喜欢
  • 1970-01-01
  • 2015-11-30
  • 2011-07-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-03-12
相关资源
最近更新 更多