同步发生在成员类还是包装类中?

问题描述:

首先,我读过通过:同步发生在成员类还是包装类中?

我跟随无数的链接重复列出大多数这些职位。所以我提前道歉,如果这是重复的。我不觉得我的问题被任何这些或其后的链接回答。但是现在我又问了,因为我不知道这里发生了什么。现在的主要事件...

我有一对类,ABB类具有A实例作为成员:

A类:

public class A { 
    private final int count; 

    public A(int val) { 
     count = val; 
    } 

    public int count() { return count; } 

    public A incrementCount() { 
     return new A(count + 1); 
    } 

    public void doStuff(long i) { 
     try { 
      Thread.sleep(i * 100); 
     } catch (Exception e) { 
      e.printStackTrace(); 
     } 
    } 
} 

类B:

public class B implements Runnable{ 
    private A a; 

    public B() { a = new A(0); } 

    @Override 
    public void run() { 
     for (int i = 1; i < 5; i++) { 
       a.doStuff(i); 
       a = a.incrementCount(); 
      } 
    } 
} 

我有一个类,需要B的一个实例,并传递它到两个线程,启动两个线程,然后让他们做他们的事情:

public class App { 
    public static void main(String[] args) { 
     B b = new B(); 
     Thread b1 = new Thread(b, "b1"); 
     b1.start(); 
     Thread b2 = new Thread(b, "b2"); 
     b2.start(); 
     try { 
      b1.join(); 
      b2.join(); 
     } catch (InterruptedException e) { 
      e.printStackTrace(); 
     } 
    } 
} 

我期望的是,对于由b,count包含的A的实例,其顺序从0递增到8。

但此代码:

synchronized public A incrementCount() { 
    return new A(count + 1); 
} 

synchronized public void doStuff(long i) { 
    try { 
     Thread.sleep(i * 100); 
    } catch (Exception e) { 
     e.printStackTrace(); 
    } 
} 

或本规范(等效于上面,我认为):

public A incrementCount() { 
    synchronized (this) { 
     return new A(count + 1); 
    } 
} 

public void doStuff(long i) { 
    synchronized (this){ 
     try { 
      Thread.sleep(i * 100); 
     } catch (Exception e) { 
      e.printStackTrace(); 
     } 
    } 
} 

我得到的结果是这样的:

THREAD| OBJECT  |COUNT 
---------------------------- 
main |[email protected]| 0  
b1 |[email protected]| 1  
b1 |[email protected]| 2  
b2 |[email protected]| 2  
b2 |[email protected]| 3  
b1 |[email protected]| 4  
b2 |[email protected]| 4  
b1 |[email protected]| 5  
b2 |[email protected]| 5  

显然,东西是不对的。我也认为值得注意的是,尽管有重复的数字,但这些对象似乎都是独特的对象。

但对于这个代码(A类):

private final Object incrementLock = new Object(); 
private final Object doStuffLock = new Object(); 

... 

public A incrementCount() { 
    synchronized (incrementLock) { 
     return new A(count + 1); 
    } 
} 

public void doStuff(long i) { 
    synchronized (doStuffLock){ 
     try { 
      Thread.sleep(i * 100); 
     } catch (Exception e) { 
      e.printStackTrace(); 
     } 
    } 
} 

或本规范(B类):

@Override 
synchronized public void run() { 
    for (int i = 1; i < 5; i++) { 
     a.doStuff(i); 
     a = a.incrementCount(); 
    } 
} 

我得到的结果我想到:

THREAD| OBJECT  |COUNT 
------------------------------ 
main |[email protected] | 0  
b1 |[email protected] | 1  
b2 |[email protected] | 2  
b1 |[email protected] | 3  
b2 |[email protected]| 4  
b1 |[email protected]| 5  
b2 |[email protected] | 6  
b1 |[email protected]| 7  
b2 |[email protected] | 8 

由于只有一个对象被两个线程访问(b1b2),为什么aren 't synchronized (this)synchronized public...锁定对象实例,阻止两个线程进入同步块并破坏count,可以这么说吗?或者我错过了什么?

您应该同步B中的代码,其中您有多个线程会突变状态(实例变量a)。同步A中的方法是没有意义的,因为类的实例实际上只是不可变的值对象。

A同步上this方法,在代码中最棘手的部分是这样的:

a = a.incrementCount(); 

因为有您泄漏监视器类之外,并重新分配这是抱着它的变量。

即使使用不同的监控对象的两种方法的A版本似乎工作,有一个竞争条件(你可以看到,如果你添加更多的线程和迭代步骤和降低/消除doStuff()睡眠时间)因为没有什么能保证在上面的代码中正确增加了a

使代码线程安全的唯一方法是同步run()方法B

+0

有没有什么机会让我说服你详细说明你的意思* ......在课堂外泄漏显示器*? – nihilon 2015-03-19 14:19:06

incrementCount()中,您每次创建一个新的A实例时,几乎都会损害您的整体同步想法。这是你的问题。只需增加计数器,不要每次更换/重新创建A实例。