Java - 非最终字段上的同步

时间:2014-04-09 14:54:48

标签: java multithreading synchronization

如果在可以更改其引用的字段上进行同步,则如下所述:

class A {

    Object someObject;

    void method(){
        synchronized (someObject) {
              Object newObject = new Object();
              //possibly modify the old ref
              if(chackCondition()){
                   someObject = newObject;
              }
        }
    }
}

我担心这里可能会有一些奇怪的,低级的编译器优化,这可能让两个线程同时访问同步区域。任何评论都会得到很大的评价。

4 个答案:

答案 0 :(得分:8)

你是对的。当newObject作为someObject的新值暴露给其他线程时,那些其他线程现在使用完全不同的互斥锁,因此可以同时执行“互斥”代码。

一个常见的解决方案是只提供一个不同的互斥锁并同步:

private final Object someObjectMutex = new Object();

答案 1 :(得分:0)

这里没有优化。如果someObject对象引用与它们不同,那么两个或多个线程不会在该点同步。这就是对象应该是final字段的原因。此外,如果必须在该点同步使用A的不同实例的所有线程,请将该字段标记为static

答案 2 :(得分:0)

没有必要担心低级编译器优化,因为它只是一个编程错误。锁定对象就像信号量:它们同步查看它们的线程的动作,但如果两个驱动程序查看两个不同的信号量,它们将不再同步。

我说这是一个编程错误,因为你永远不想改变信号量。如果你需要在获得锁之后修改字段,那么你实际上可能会使用相同的对象来实现两个(或更多)不同的目的,这是一个设计错误(注意你不要修改对象,您更改字段的值 - 如果对象本身发生变异则没有问题。)

顺便说一句,看看java.util.concurrent中的帮助者,例如ReentrantLock

答案 3 :(得分:0)

问题中的代码段不应该被调用为同步,因为对锁/同步字段使用非最终字段是一种不好的做法。

良好做法:使用最终对象锁定/同步任何关键部分,这将有助于我们避免对锁定对象进行任何无意的更新。

当我们正在同步我们的关键代码段的对象被新对象更新时,其他线程将不会同步到旧对象,因此处理旧对象的所有线程将继续以同步模式工作但是其他线程不会同步,因为它们不再共享同一个锁。

为了更好地理解,请参阅以下代码: -

import java.util.Date;


public class SynchronizingNonFinalField {
    public static void main(String[] args) {
        SharedObject sharedObject = new SharedObject();
        MyIntendedThreadRunner runner = new MyIntendedThreadRunner(sharedObject);
        Thread myThread1 = new Thread(runner);
        Thread myThread2 = new Thread(runner);
        Thread myThread3 = new Thread(runner);
        Thread myThread4 = new Thread(runner);
        Thread myThread5 = new Thread(runner);
        Thread myThread6 = new Thread(runner);
        Thread myThread7 = new Thread(runner);
        myThread1.start();
        myThread2.start();
        myThread3.start();
        myThread4.start();
        myThread5.start();
        myThread6.start();
        runner.setSharedObject(new SharedObject());
        myThread7.start();
    }
}

class MyIntendedThreadRunner implements Runnable {
    private SharedObject sharedObject;
//    final private SharedObject sharedObject;

    public MyIntendedThreadRunner(SharedObject sharedObject) {
        this.sharedObject = sharedObject;
    }

    @Override
    public void run() {
        synchronized (sharedObject) {
            sharedObject.increment10mins();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // Although this doesn't seem to be practical here, but if we have some complex requirement,
            // then a thread unconsciously changed the shared object, so we should mark it as final
            sharedObject = new SharedObject();
            System.out.println(sharedObject.getObj());
        }
    }

    public void setSharedObject(SharedObject sharedObject) {
        this.sharedObject = sharedObject;
    }
}

class SharedObject {
    private Date obj;

    public SharedObject() {
        this.obj = new Date();
    }

    public Date getObj() {
        return obj;
    }

    public void setObj(Date obj) {
        this.obj = obj;
    }

    public void increment10mins() {
        obj.setTime(obj.getTime() + (10 * 60 * 1000));
    }
}