Java线程 - 同步问题

时间:2010-05-03 18:32:46

标签: java multithreading synchronization

来自Sun的教程:

  

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

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

10 个答案:

答案 0 :(得分:5)

为了理解Java中的并发性,我建议使用宝贵的Java Concurrency in Practice

在回答您的具体问题时,尽管同步所有方法是实现线程安全的快捷方式,但它根本无法很好地扩展。考虑备受诟病的Vector类。每个方法都是同步的,并且它的工作非常繁琐,因为迭代仍然不是线程安全的。

答案 1 :(得分:3)

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

答案 2 :(得分:2)

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

修改
没有全面的教程,每种情况都是独一无二的。学习它就像学习一门外语:永远不会结束:)

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

如果其他人有任何链接,我也有兴趣看到。我发现整个主题非常混乱(可能是核心java中最困难的部分),特别是因为java 5中引入了新的并发机制。

玩得开心!

答案 3 :(得分:1)

最常见的形式是。

不需要同步不可变对象。

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

答案 4 :(得分:1)

已同步 methodName 与已同步(对象)

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

虽然差异可能很微妙,但如果在单个线程中使用同一个对象

会很有用

即(在方法上使用synchronized关键字)

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关键字会运行得更快。

那么,您将如何在多线程应用程序中执行此操作?

您只需同步对象:

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

Vector vs ArrayList

作为补充说明,这种用法( syncrhonized methodName与syncrhonized(object))顺便说一句,java.util.Vector现在被{{1替换的原因之一}}。许多java.util.ArrayList方法都是同步的。

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

Vector替换为Hashtable

答案 5 :(得分:1)

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

答案 6 :(得分:1)

没有。即使是吸气剂也必须同步,除非他们只访问最终字段。原因是,例如,当访问一个long值时,另一个线程当前正在写入一个微小的变化,并且只读了前4个字节而其他4个字节仍然是旧值时读取它。

答案 7 :(得分:0)

是的,这是正确的。修改数据或访问可能由不同线程修改的数据的所有方法都需要在同一监视器上同步。

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

答案 8 :(得分:0)

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

答案 9 :(得分:0)

您可以使用同步方法,同步块,并发工具(例如Semaphore),或者如果您真的想要陷入困境,可以使用原子参考。其他选项包括将成员变量声明为volatile并使用AtomicInteger等类而不是Integer

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

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

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