2010-05-03 144 views
2

从Sun的教程:Java线程 - 同步问题

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

Q1。上述语句是否意味着如果某个类的对象将在多个线程之间共享,那么该类的所有实例方法(final字段的getter除外)都应该同步化,因为实例方法处理实例变量?

回答

5

被替换为了解并发在Java中,我推荐的宝贵Java Concurrency in Practice

为了回应您的具体问题,虽然同步所有方法是一种快速和肮脏的方式来实现线程安全,但它根本不能很好地扩展。考虑大量恶意的Vector类。每个方法都是同步的,并且它工作起来非常糟糕,因为迭代仍然不是线程安全的。

0

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

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

+0

你能提供一个使用wait()和notify()以及synchronized语句的例子吗? – 2010-05-03 18:55:08

0

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

+0

除了setter和getter之外,其他实例方法也处理可共享的数据。那么我应该同步所有访问/写入可共享数据的实例方法吗? – 2010-05-03 18:57:22

3

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

+0

可以请告诉我三件事情。首先,那些可用于线程安全的其他方式是什么(我认为volatile变量是其中的一个)。其次,同步方法在哪些情况下失败?第三,你认为我们遇到的同步有哪些问题? – 2010-05-03 18:52:11

+1

线程安全的另一种方法是不变性。请注意,如果符合以下条件,则可以将您的对象视为不可变: 1.其所有基元都是最终的 2.其所有非基元都是不可变的。 另外重要的是要明白,它并不容易实现真正的线程安全。当每个单独的操作可能是线程安全的时候,有一个condifional线程安全,但某些操作序列可能需要外部同步。典型的例子是Hashtable。你可以在这里找到关于这个主题的更多信息:http://www.ibm.com/developerworks/java/library/j-jtp09263.html – wax 2010-05-03 19:26:17

+0

@Goel:1)volatile变量,线程和堆栈限制,java.util.concurrent包等; 2)僵局; 3)唯一的“问题”本身就是阿姆达尔的法则。我也推荐Goetz的书“Java Concurrency in Practice”。 – 2010-05-06 19:20:46

2

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

编辑
上有没有全面的教程,每一种情况是独一无二的。学习它就像学习外语一样:永不结束:)

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

如果其他人有任何联系我很想也能看到。我发现整个话题相当混乱(并且可能是java核心中最困难的部分),尤其是因为java 5中引入了新的并发机制。

玩得开心!

+0

你能提供一些关于这个教程的链接吗? – 2010-05-03 18:53:20

+0

编辑答案以允许更好的格式化,例如linebrakes:/ – 2010-05-03 22:54:42

+0

+1。它真的帮助了我。 – 2010-05-04 09:35:51

1

以最一般的形式是。

不可变对象不需要同步。

此外,您还可以使用的可变实例变量各监视器/锁(或团体存在的),这将有助于活力。以及只能同步数据更改的部分,而不是整个方法。

0

您可以使用同步方法,同步块,并发工具(如Semaphore),或者如果您确实想要弄脏并弄脏,可以使用原子引用。其他选项包括声明成员变量为volatile,并使用类似AtomicInteger而不是Integer

这一切都取决于不同的情况,但也有广泛的可并发工具 - 这些只是其中的一部分。

同步可能导致保持观望僵局,其中每两个线程有​​一个对象的锁,并试图获取其他线程的对象的锁。

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

1

同步方法名 VS同步的(对象)

这是正确的,并且是一个备选。我认为只有同步所有方法才能更有效地同步对该对象的访问。

虽然差可以是微妙的,如果使用了在单一线程相同的对象

即(使用方法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 (someObject) { 
    someObject.click(); 
} 

矢量VS的ArrayList

作为附加的注释,这种用法(syncrhonized方法名与synchonized(object))顺便说一下,java.util.Vector现在被java.util.ArrayList取代的原因之一。许多Vector方法是同步的。

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

也是一样Hashtable通过HashMap

+0

请注意,您还需要同步创建'someObject'变量,或让它由主线程创建。 – Finbarr 2010-05-03 19:01:01

+0

@finbarr在多线程应用中,'someObject'必须由主线程创建。如果没有,那么我认为它不会在从主线程开始的线程之间共享,并且如果它不可共享,则不需要同步它的创建。 – 2010-05-03 19:06:37

+0

@Oscar您已经指出了一个很好的观点,但应用程序可以是单线程应用程序或多线程应用程序。所以,如果我编码一个单线程应用程序,那么为什么我需要同步任何东西。如果我编写一个多线程应用程序,那么为什么我不应该与整个实例方法或这些实例方法的一部分同步,而不是在每个地方同步'someObject.click()'。 – 2010-05-03 19:10:05

1

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

1

不可以。即使getter也必须同步,除非他们只访问final字段。原因在于,例如,当访问一个长整型值时,其他线程当前写入的内容会发生一些微小的变化,并且只读取前四个字节,而其他4个字节保持旧值。

+0

...但最终字段的获取者不会,因为他们的值永远不会改变。 – paulcm 2010-05-04 19:09:53

+0

对不起,我读了描述中的getters“和”final字段。你是对的。 – Daniel 2010-05-04 19:13:23