【问题标题】:Java - synchronization on non-final fieldJava - 非最终字段上的同步
【发布时间】:2014-04-09 14:54:47
【问题描述】:

在可以改变其引用的字段上进行同步的情况,如下所述:

class A {

    Object someObject;

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

我担心这里可能缺少一些奇怪的低级编译器优化,这可能会让两个线程同时访问同步区域。任何 cmets 都会非常感激。

【问题讨论】:

  • 那你为什么不使用另一个对象作为锁呢?
  • 这只是一个例子,让我思考。我知道我应该使用ReenetrantLock 或其他锁定对象,但这只是为了我的理解

标签: java multithreading synchronization


【解决方案1】:

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

一个常见的解决方案是只提供一个不同的互斥体并在其上进行同步:

private final Object someObjectMutex = new Object();

【讨论】:

  • 是的,但这是代码的最后一行,所以我并不关心,因为我的线程不会在互斥锁部分执行任何进一步的操作,对吧?
  • @Bober02 你实际上并不知道;如果没有共同的同步屏障,线程之间就没有happens-before,特别是没有要求在最后一次赋值之前的指令实际上在它之前执行。
  • @Bober02 无法想象三个线程,T1 已获取 someObject 并且 T2 正在等待 someObject 的锁可用。然后T1修改someObject为newObject,T3获取newObject并进入block。 T1 释放 someObject,让 T2 也进入区块。您现在有两个线程同时执行您的块。
  • @assylias,非常感谢,这就是我需要的示例 :) 愚蠢的我,多线程比看起来要复杂得多......
  • @assylias:很好的反例!
【解决方案2】:

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

【讨论】:

  • final 是,static 否。
  • static 仅当类的所有实例的锁必须相同时,这不是最常见的情况
【解决方案3】:

不应将所问问题中的代码 sn-p 称为同步,因为将非最终字段用于锁定/同步字段是一种不好的做法。

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

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

为了更好的理解,请看下面的代码:-

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

【讨论】:

    【解决方案4】:

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

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

    顺便说一句,看看java.util.concurrent 中的助手,比如ReentrantLock

    【讨论】:

      猜你喜欢
      • 2015-12-27
      • 1970-01-01
      • 1970-01-01
      • 2013-04-20
      • 2023-04-10
      • 1970-01-01
      • 1970-01-01
      • 2012-07-13
      • 1970-01-01
      相关资源
      最近更新 更多