【问题标题】:Java Thread - Synchronization issueJava 线程 - 同步问题
【发布时间】:2010-05-03 18:32:46
【问题描述】:

来自 Sun 的教程:

同步方法启用了一种防止线程干扰和内存一致性错误的简单策略:如果一个对象对多个线程可见,则对该对象变量的所有读取或写入都通过同步方法完成。 (一个重要的例外:final 字段,在构造对象后无法修改,可以通过非同步方法安全地读取,一旦对象被构造)这种策略是有效的,但会带来活性问题,正如我们将请参阅本课后面的内容。

第一季度。上面的语句是否意味着如果一个类的对象要在多个线程之间共享,那么应该使该类的 所有实例方法(最终字段的 getter 除外)同步,因为实例方法流程实例变量?

【问题讨论】:

    标签: java multithreading synchronization


    【解决方案1】:

    为了理解Java中的并发,我推荐无价的Java Concurrency in Practice

    针对您的具体问题,虽然同步所有方法是实现线程安全的一种快速而简单的方法,但它根本不能很好地扩展。考虑一下备受诟病的 Vector 类。每个方法都是同步的,而且效果非常好,因为迭代仍然不是线程安全的。

    【讨论】:

      【解决方案2】:

      没有。这意味着同步方法是实现线程安全的一种方法,但它们不是唯一的方法,而且它们本身并不能保证在所有情况下都完全安全。

      【讨论】:

      • 请告诉我三件事。首先,还有哪些其他可用于线程安全的方法(我认为 volatile 变量就是其中之一)。其次,在什么情况下同步方法会失败?第三,您认为我们在同步方面遇到了哪些问题?
      • 线程安全的另一种方式是不变性。请注意,如果您的对象满足以下条件,则可以认为它是不可变的: 1. 它的所有原语都是最终的 2. 它的所有非原语都是不可变的。同样重要的是要了解实现真正的线程安全并不容易。当每个单独的操作可能是线程安全的时,存在条件线程安全,但某些操作序列可能需要外部同步。典型的例子是哈希表。您可以在此处找到有关此主题的更多信息:ibm.com/developerworks/java/library/j-jtp09263.html
      • @Goel: 1) volatile 变量、线程和堆栈限制、java.util.concurrent 包等; 2)死锁; 3) 就其本身而言,唯一的“问题”是阿姆达尔定律。我也推荐 Goetz 的书《Java Concurrency in Practice》。
      【解决方案3】:

      不一定。例如,您可以同步(例如,在专用对象上加锁)访问对象变量的方法的一部分。在其他情况下,您可以将作业委托给一些已经处理同步问题的内部对象。
      有很多选择,这完全取决于您正在实施的算法。虽然,“同步”关键字通常是最简单的关键字。

      编辑
      没有全面的教程,每种情况都是独一无二的。学习它就像学习一门外语:永无止境:)

      但肯定有有用的资源。特别是在 Heinz Kabutz 的网站上有一系列有趣的文章。
      http://www.javaspecialists.eu/archive/Issue152.html (查看页面上的完整列表)

      如果其他人有任何链接,我也有兴趣查看。我发现整个主题相当混乱(并且可能是核心 java 中最困难的部分),尤其是在 java 5 中引入了新的并发机制之后。

      玩得开心!

      【讨论】:

      • 你能给我一些关于这方面的教程链接吗?
      【解决方案4】:

      以最一般的形式是的。

      不可变对象不需要同步。

      此外,您可以为可变实例变量(或其中的组)使用单独的监视器/锁,这将有助于提高活力。以及仅同步数据更改的部分,而不是整个方法。

      【讨论】:

        【解决方案5】:

        同步方法名 vs 同步(对象)

        这是正确的,也是一种选择。我认为只同步对该对象的访问而不是同步它的所有方法会更有效。

        虽然差异可能很细微,但如果您在单个线程中使用相同的对象会很有用

        ie(在方法上使用同步关键字)

        class SomeClass {
            private int clickCount  = 0;
        
            public synchronized void click(){
                clickCount++;
            }
         }
        

        当这样定义一个类时,一次只有一个线程可以调用click 方法。

        如果在单线程应用程序中过于频繁地调用此方法会怎样?您将花费一些额外的时间检查该线程是否可以在不需要时获得对象锁。

        class Main {
            public static void main( String  [] args ) {
                 SomeClass someObject = new SomeClass();
                 for( int i = 0 ; i < Integer.MAX_VALUE ; i++ ) {
                     someObject.click();
                 }
            }
         }
        

        在这种情况下,检查线程是否可以锁定对象将被不必要地调用Integer.MAX_VALUE (2 147 483 647) 次。

        因此在这种情况下删除同步关键字会运行得更快。

        那么,您将如何在多线程应用程序中做到这一点?

        你只是同步对象:

        synchronized ( someObject ) {
            someObject.click();
        }
        

        向量与数组列表

        作为附加说明,这种用法(syncrhonized methodName vs.syncrhonized(object))顺便说一句,是java.util.Vector 现在被java.util.ArrayList 取代的原因之一。许多Vector 方法是同步的。

        大多数情况下,列表用于单线程应用程序或一段代码(即 jsp/servlets 中的代码在单线程中执行),而 Vector 的额外同步对性能没有帮助。

        Hashtable 被替换为 HashMap 也是如此

        【讨论】:

        • 请注意,您还需要同步 someObject 变量的创建,或者让其由主线程创建。
        • @finbarr 在多线程应用程序中,someObject 必须由主线程创建。如果不是,那么我认为它不会在从主线程开始的线程之间共享,如果它是不可共享的,则不需要同步它的创建。
        • @Oscar 您指出了一个很好的观点,但应用程序可以是单线程应用程序或多线程应用程序。因此,如果我正在编写单线程应用程序,那么我为什么需要同步任何东西。如果我正在编写一个多线程应用程序,那么为什么我不应该同步整个实例方法或这些实例方法的一部分,而不是在每个地方同步 someObject.click()
        • @Yatendra:因为您可能需要一段在两者上都运行的代码。如果您确定将在哪种情况下使用您的代码,那么采用一种方法或另一种方法是正确的。但是,如果要在未知场景中重用您的代码,那么您应该明智地选择,因为之后您将无法更改(请参阅:如何设计一个好的 API 及其重要性:YouTube 上的youtube.com/watch?v=aAb7hSCtvGw )但这只是另一种选择,这就是为什么我开始回答:“......这是正确的,并且是一种选择。我认为它会......” :)
        【解决方案6】:

        事实上,getter a 也应该同步,否则字段将被设为volatile。那是因为当您获得一些价值时,您可能对该价值的最新版本感兴趣。你看,synchronized 块语义不仅提供了执行的原子性(例如,它保证一次只有一个线程执行这个块),而且还提供了可见性。这意味着当线程进入synchronized 块时,它会使其本地缓存无效,而当它退出时,它会将所有已修改的变量转储回主内存。 volatile 变量具有相同的可见性语义。

        【讨论】:

          【解决方案7】:

          没有。即使是 getter 也必须同步,除非它们只访问 final 字段。原因是,例如,当访问一个 long 值时,另一个线程当前正在写入它,您读取它时只写入了前 4 个字节,而其他 4 个字节仍然是旧值。

          【讨论】:

          • ...但最终字段的 getter 不会,因为它们的值永远不会改变。
          • 抱歉,我在描述中读到了 getter "和" final 字段。你是对的。
          【解决方案8】:

          是的,没错。所有修改数据或访问可能被不同线程修改的数据的方法都需要在同一个监视器上同步。

          简单的方法是将方法标记为同步。如果这些是长时间运行的方法,您可能只想同步读/写的那部分。在这种情况下,您将定义监视器以及 wait() 和 notify()。

          【讨论】:

          • 你能提供一个使用 wait() 和 notify() 以及同步语句的例子吗?
          【解决方案9】:

          简单的答案是肯定的。 如果类的一个对象要被多个线程共享,则需要同步 getter 和 setter 以防止数据不一致。 如果所有线程都有单独的对象副本,则无需同步方法。如果您的实例方法不仅仅是 set 和 get,您必须分析线程等待长时间运行的 getter/setter 完成的威胁。

          【讨论】:

          • 除了 setter 和 getter,其他实例方法也处理那些可共享的数据。那么我应该同步所有访问/写入可共享数据的实例方法吗?
          【解决方案10】:

          您可以使用同步方法、同步块、并发工具(例如Semaphore),或者如果您真的想搞砸,您可以使用原子引用。其他选项包括将成员变量声明为volatile 并使用AtomicInteger 之类的类而不是Integer

          这完全取决于具体情况,但有很多可用的并发工具 - 这些只是其中的一部分。

          同步可能导致保持等待死锁,其中两个线程各自拥有一个对象的锁,并试图获取另一个线程的对象的锁。

          同步对于一个类也必须是全局的,一个容易犯的错误是忘记同步一个方法。当一个线程持有一个对象的锁时,其他线程仍然可以访问该对象的非同步方法。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2011-01-17
            • 1970-01-01
            • 1970-01-01
            • 2020-02-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多