在Java中的线程之间共享对象的最佳实践或原则

时间:2017-02-28 12:20:37

标签: java multithreading

我试图搜索但找不到我正在寻找的确切答案,因此提出了一个新问题。

如果您希望在多个线程之间共享任何可变对象,是否有任何最佳实践/原则/指南可以执行此操作?

或者它会根据具体情况而变化吗?

5 个答案:

答案 0 :(得分:5)

在线程之间共享可变对象是有风险的。

最安全的方法是使对象不可变,然后你可以自由地共享它们。

如果它们必须是可变的,那么每个对象都需要使用通常的方法来确保它们自己的线程安全。 (synchronizedAtomixX等)。

保护单个对象的方法会有很大差异,但取决于您使用它们的方式以及使用它们的方式。

答案 1 :(得分:4)

在java中,您应该同步任何更改/读取共享对象状态的方法,这是最简单的方法。

其他策略是:

  • 使用线程安全类(ConcurrentHashMap),例如
  • 使用锁
  • 使用volatile关键字,以避免过时的对象(有时可以用作轻量级同步器)

他们的关键是同步您的更新/读取以保证一致的状态,您的方式可能会有很大差异。

答案 2 :(得分:3)

线程之间共享对象的问题是由两者造成的 线程同时改变相同的数据结构。这不一定是个问题,你只需要计划所有结果。

这些是我使用的策略。

  1. 尽可能使用不可变对象。
  2. 这消除了完全更改数据结构的问题。然而,有许多有用的模式无法用这种方法编写。除非你使用促进不变性的语言/ api,否则它可能效率低下。将条目添加到Scala列表要比制作Java列表的副本并向副本添加条目快得多。

    1. 使用synchronize关键字。
    2. 这确保了一次只允许一个线程更改对象。选择要同步的对象非常重要。更改结构的一部分可能会将孔结构置于非法状态,直到进行另一次更改。同步还可以消除多线程的许多好处。

      1. 演员模特。
      2. 演员模型在演员中组织世界,向对方发送不可变消息。每个actor只有一个线程。演员可以包含可变性。 像Akka这样的平台为这种方法提供了基础。

        1. 使用原子类。 (java.util.concurrent.atomic中)
        2. 这些宝石有像incrementAndGet这样的方法。它们可以使用 在没有开销的情况下实现同步的许多效果。

          1. 使用并发数据结构。
          2. Java api包含为此目的而创建的并发数据结构。

            1. 冒两次风险。
            2. 在编写缓存时,通常最好冒两次工作而不是使用同步。假设你有来自dsl的已编译表达式的缓存。如果表达式被编译两次就好了,只要它最终在缓存中结束。通过允许在初始化期间执行一些额外的工作,您可能不需要在缓存访问期间使用synchronize关键字。

答案 3 :(得分:1)

有例子。 StringBuilder不是线程安全的,因此没有synchronized (builder)块 - 结果将被破坏。试试看。

某些对象是线程安全的(例如StringBuffer),因此不需要使用synchronized块。

    public static void main(String[] args) throws InterruptedException {
        StringBuilder builder = new StringBuilder("");

        Thread one = new Thread() {
            public void run() {

                for (int i = 0; i < 1000; i++) {
                    //synchronized (builder) {
                        builder.append("thread one\n");
                    //}
                }
            }
        };
        Thread two = new Thread() {
            public void run() {

                for (int i = 0; i < 1000; i++) {
                    //synchronized (builder) {
                        builder.append("thread two\n");
                    //}
                }
            }
        };
        one.start();
        two.start();
        one.join();
        two.join();

        System.out.println(builder);
    }

答案 4 :(得分:1)

虽然已经发布了一些好的答案,但这是我在阅读Java Concurrency in Practice第3章 - 分享对象时找到的内容。

从书中引用。

  

对象的发布要求取决于其可变性:

     
      
  • 可变对象可以通过任何机制发布;
  •   
  • 必须安全发布有效的不可变对象(发布后状态不会被修改);
  •   
  • 必须安全发布可变对象,并且必须是线程安全的或通过锁保护。
  •   

本书介绍了安全发布可变对象的方法:

  

要安全地发布对象,必须同时使对象的引用和对象的状态对其他线程可见。正确构造的对象可以通过以下方式安全地发布:

     
      
  • 从静态初始值设定项初始化对象引用;
  •   
  • 将对它的引用存储到易失性字段或AtomicReference中;
  •   
  • 将对它的引用存储到正确构造的对象的最终字段中;或
  •   
  • 将对它的引用存储到由锁定正确保护的字段中。
  •   

最后一点是指使用各种机制,例如使用并发数据结构和/或使用synchronize关键字。