同步应该发生在成员 class 还是包装器 class 中?

Should synchronization happen in the member class or in the wrapper class?

首先,我读过的内容:

我点击了无数链接,指向大多数帖子中列出的重复项。所以如果这是重复的,我提前道歉。我觉得我的问题没有被这些或后续链接中的任何一个回答。但话又说回来,我现在问是因为我不知道这里发生了什么。现在进入主要活动...

我有一对 classes,AB。 Class B 有一个 A 的实例作为成员:

Class答:

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();
        }
    }
}

Class乙:

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();
            }
    }
}

我有一个 class 获取 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 包含的 A 实例,count 从 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  |testbed.A@11121|  0    
b1    |testbed.A@64f6c|  1    
b1    |testbed.A@87238|  2    
b2    |testbed.A@2bb51|  2    
b2    |testbed.A@17d5d|  3    
b1    |testbed.A@16fa4|  4    
b2    |testbed.A@95c08|  4    
b1    |testbed.A@191d8|  5    
b2    |testbed.A@2d9c0|  5    

显然,有些不对劲。我还认为值得注意的是,即使有重复的数字,这些对象看起来都是唯一的对象。

但对于此代码(在 Class 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();
        }
    }
}

或此代码(在 Class B 中):

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

我得到了我期望的结果:

THREAD|    OBJECT       |COUNT
------------------------------
main  |testbed.A@f7f540 |  0    
b1    |testbed.A@64f6cd |  1    
b2    |testbed.A@872380 |  2    
b1    |testbed.A@2bb514 |  3    
b2    |testbed.A@17d5d2a|  4    
b1    |testbed.A@16fa474|  5    
b2    |testbed.A@95c083 |  6    
b1    |testbed.A@191d8c1|  7    
b2    |testbed.A@2d9c06 |  8

既然只有一个对象被两个线程(b1b2)访问,为什么 synchronized (this)synchronized public... 不锁定对象实例,防止两个线程进入同步块并破坏count,可以这么说吗?还是我漏掉了什么?

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

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

A 中同步 this 上的方法时,代码中最有问题的部分是:

a = a.incrementCount();

因为你在 class 之外泄漏了监视器并重新分配了持有它的变量。

尽管 A 的版本对两种方法使用不同的监视器对象似乎都有效,但存在竞争条件(如果您添加更多线程和迭代步骤和 reduce/eliminate doStuff()) 中的休眠时间,因为没有任何东西可以保证在上面的代码中分配了正确递增的 a

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