2015-02-10 111 views
1

我一直在阅读很多关于线程的知识,但我不确定处理这种情况的正确方法。可以说我有一个引用对象的实用程序类。该实用程序类并在对象上工作,它可以从多个线程如何制作此线程安全

class Utility{ 
    Foo xx; 
    public Utility(Foo xx){ 
     this.xx = xx; 
    } 

    public void doWork(){ 
     x.action(); 
    } 
    } 

现在,如果我有两个线程将要访问的实用工具类和调用的doWork(),其中xx总是指同一被称为对象,我怎样才能使它线程安全?

编辑:有2个实例类(每个线程一个)的实例,但相同的引用xx对象。请参阅编辑:

public class ThreadSafety { 

    public static void main(String[] args) throws InterruptedException { 
     Foo xx = new Foo(); 
     Utility util1 = new Utility(xx); 
     Utility util2 = new Utility(xx); 
     Thread t1 = new Thread(new MyRunnable(util1) , "t1"); 
     Thread t2 = new Thread(new MyRunnable(util2) , "t2"); 

     t1.start(); 
     t2.start(); 
     t1.join(); 
     t2.join(); 

    } 

} 

class MyRunnable implements Runnable{ 
    Utility util; 

    @Override 
    public void run() { 
     util.doWork(); 
    } 


    private void MyRunnable (Utility util) { 
     this.util = util; 
    } 

} 
+0

请注意,如果doWork方法不会更改Foo实例的状态(特别是在Foo不可变的情况下),则不需要同步。 – sudocode 2015-02-10 16:11:27

+0

+1 @JamesWatson给出了让你把'xx'私人化的唯一答案。没有必要提供同步方法来操作其他方法可以在没有同步的情况下访问的数据。 – 2015-02-10 16:52:12

回答

3
public class Utility{ 
    private final Foo xx; 

    public Utility(Foo xx){ 
    this.xx = xx; 
    } 

    public void doWork() { 
    // ensures only one thread can be calling action 
    synchronized(xx) { 
     xx.action(); 
    } 
    } 
} 

一些变化如下:

  1. xxfinalprivate。使它private强制执行对如何访问xx的控制。
  2. synchronize on xx致电doWork。您可以将​​放在方法声明上,但我更愿意明确说明您锁定的是哪个对象。请注意,这不会“同步xx”。它只是使用xx作为锁。在访问有潜在危险的代码块的任何地方,都必须使用同步的块与同一监视器对象。
+0

即使创建了Utility的多个实例但引用了相同的Foo,这种方法是否还能正常工作?它在我的编辑 – Snake 2015-02-10 16:29:22

+0

问题其实是的。我最初并没有考虑这个问题,但是这个版本**将**与引用相同Foo实例的Utility的多个实例一起工作。在方法**上使用'synchronized'不会**在这种情况下工作。原因在于对象级别的方法上的“synchronized”与说'synchronized(this){// method body}'的地方相同,其中this是Utility实例。在这种情况下,即使Foo实例相同,这些线程也会锁定在不同的('Utility')对象上,这样**不会阻止它们同时在同一个Foo上运行'doWork'。 – 2015-02-10 16:38:13

+0

所以如果我理解正确。将方法本身同步不起作用,因为它是多个实例。但如果我把xx同步到你的答案中,那么它会工作,因为你锁定了被引用的对象。正确? – Snake 2015-02-10 17:32:48

2

您可以使用​​关键字。

public synchronized void doWork(){ 
     x.action(); 
    } 

这将使该方法线程安全。更多关于Oracle documentation的同步方法。

随着更新的问题: 使用静态方法作为同步,然后锁将在类不是个别对象。

+0

请参阅编辑问题,您的方式仍然适用,因为它会是两个不同的实例吗? – Snake 2015-02-10 16:23:12

+0

答案是否定的。如果是util方法,可能你希望它作为'static'吗?然后它会工作。 – nebula 2015-02-10 16:31:37

+0

这里使用'static'是矫枉过正的,因为它会阻止所有**实例的'Foo'通过'doWork'的任何实用工具被访问。在'Foo'实例上使用明确的'synchronize'块应该可以解决这个问题。我从来没有在方法声明中使用'synchronized'的语法糖。我相信这是造成Java同步困惑的主要原因。 – 2015-02-10 16:47:13

3

Java有一个​​关键字,您可以在这种情况下使用。

public synchronized void doWork() 

阅读

  • 有一个在Java Docs关键字的完整说明。
+0

请参阅编辑问题,您的方式仍然适用,因为它会是两个不同的实例? – Snake 2015-02-10 16:22:35

+0

@Snake在你已经收到答案后,不要完全改变这个问题 - 理由应该很明显。也没有它不会。 – Voo 2015-02-10 16:28:43

+0

我没有完全改变它。我说得更清楚了,因为它似乎假设它是相同的效用类。我其实只是澄清说,它不止一个公用事业类。抱歉!!! – Snake 2015-02-10 17:29:47

2

这里是另一种提醒:不要使用​​。使用同步的代码往往是复杂的,错误的,并且无法测试。你会遇到问题,你永远不会知道什么时候会炸毁,甚至当它运行时,你可能会遭受糟糕的表现。做到这一点,而不是:

创建不变类,你会打电话给你Message线程之间进行通信,并使用Message在预期其他线程存储邮件到你的主线程中处理的BlockingQueue。然后,在你的主线程中,保持从队列中弹出消息并处理它们。简单(* 1),简单,可测试,万无一失。

BlockingQueue使用​​在内部实现,它是由知道自己在做什么的人编写的,并且已经过全面测试,因此它可以工作。这是唯一应该包含​​的地方。 (* 1)起初它似乎并不像在几个地方粘贴​​关键字那样容易,但事实上,从长远来看,​​总是变得更加复杂和困难得到正确的比最初似乎。

+0

有趣......我没有太多的多线程要做。只有我担心的一个对象会被多个线程访问。然而,你推荐给这个Immutable解决方案和BlockingQueue的任何链接?如果网络上有一个直接的例子,那么我可以使用它(并接受这个答案:)) – Snake 2015-02-10 16:27:24

+0

这个答案有很多很好的信息,但我不同意使用队列和工作人员更简单。对于试图绕过多线程的人来说,使用Java进行同步就像是在基础知识中的指导课程。如果您的争用率较低,则通常比使用更新的更高级的并发选项要快得多。但是,由于线程太多,保持所有线程正常工作并使其与同步正确非常困难。对于简单的东西,如确保对象创建一次等等,这很好。 – 2015-02-10 16:34:52

+0

我不知道有什么特别好的样品脱离了我的头顶。所以,我做了一个谷歌搜索,真正快速地查看了结果,并为你挑选了一个看起来比其他人更好的结果:http://www.developer.com/java/ent/article.php/3645111/Java- 5s-BlockingQueue.htm – 2015-02-10 16:34:55