2016-07-07 45 views
9

我知道您可以通过编写对finalvolatile字段的引用来安全地发布非线程安全对象,该字段稍后将由另一个线程读取,前提是发布时线程创建该对象的对象会放弃对其的引用,以便它不再干扰或不安全地观察该对象在另一个线程中的使用。本地最终引用的安全发布

但是在这个例子中,没有明确的final字段,只有final局部变量。 如果来电者丢弃了对unsafe的引用,这是否安全发布?

void publish(final Unsafe unsafe) { 
    mExecutor.execute(new Runnable() { 
     public void run() { 
      // do something with unsafe 
     } 
    } 
} 

我发现了几个Q &作为,像this one,这表明final局部变量是隐式“复制”到匿名类。这是否意味着上面的例子等同于这个?

void publish(final Unsafe unsafe) { 
    mExecutor.execute(new Runnable() { 
     final Unsafe mUnsafe = unsafe; 
     public void run() { 
      // do something with mUnsafe 
     } 
    } 
} 

编辑澄清:

Unsafe可以是任何东西,但说这是这样的:

public class Unsafe { 
    public int x; 
} 

而且mExecutor是什么,满足Executor合同。

+1

您的执行程序正在使用线程安全队列。要看到这个问题,你必须在线程之间传递对象,而不用在任何地方使用合适的内存屏障 –

+1

@PeterLawrey class Executor {void execute(Runnable r){}}' - 这里没有队列。但问题是*可能*有效还是... – Marco13

+0

BTW'Unsafe'是具有一个单类,虽然你可以创建更多的人... –

回答

4

虽然,不可否认,我不能完全肯定,我得到了你的问题的实际点,(在评论中指出的)的问题很可能不是一个真正的问题,你的具体情况,也许相关

import java.util.concurrent.ExecutorService; 

class Unsafe 
{ 

} 

class SafePublication 
{ 
    private final ExecutorService mExecutor = null; 

    public void publish(final Unsafe unsafe) 
    { 
     mExecutor.execute(new Runnable() 
     { 
      @Override 
      public void run() 
      { 
       // do something with unsafe 
       System.out.println(unsafe); 
      } 
     }); 
    } 
} 

人们可以编译并获得两个.class文件::

    01的见解可以从测试/示例

    考虑下面的类来获得

  • SafePublication.class
  • SafePublication$1.class的内部类

反编译的内部类的类文件产生如下:

class SafePublication$1 implements java.lang.Runnable { 
    final Unsafe val$unsafe; 

    final SafePublication this$0; 

    SafePublication$1(SafePublication, Unsafe); 
    Code: 
     0: aload_0 
     1: aload_1 
     2: putfield  #1     // Field this$0:LSafePublication; 
     5: aload_0 
     6: aload_2 
     7: putfield  #2     // Field val$unsafe:LUnsafe; 
     10: aload_0 
     11: invokespecial #3     // Method java/lang/Object."<init>":()V 
     14: return 

    public void run(); 
    Code: 
     0: getstatic  #4     // Field java/lang/System.out:Ljava/io/PrintStream; 
     3: aload_0 
     4: getfield  #2     // Field val$unsafe:LUnsafe; 
     7: invokevirtual #5     // Method java/io/PrintStream.println:(Ljava/lang/Object;)V 
     10: return 
} 

我们可以看到,对于final参数,的确有场在这堂课中介绍。这个字段是val$unsafe,它是一个final field in the class file sense,它在构造函数中被初始化。 (这与您发布的第二个代码片段不完全相同,因为第二个代码片段包含两个最终字段,它们都使用相同的值进行初始化,但关于安全发布的问题,应该是一样的)。

+0

这正是我所掌握的。 –

1

的问题似乎被这个答案可以部分回答:

Java multi-threading & Safe Publication

至少关于“安全发布”。

现在,如果来电者抛弃它的参考变量将是安全的,因为存在除了在最后的局部变量没有参考变量。

以及有关代码示例 - 在我的眼睛里带着代码片段是等价的。引入一个额外的局部变量不会改变语义,在这两种情况下,编译器都会将引用识别为不可变的,并让您使用它。

编辑 - 我要离开这个部分来记录我的任择议定书的问题的误解

为了澄清 - 我采取的finalvolatile使用如本例授予,所以能见度正确的内存屏障餐饮的对象引用在那里,唯一的一点是非线程安全对象的可能的可变性,它不能使用内存障碍来保证,并且实际上与它们无关。它可以通过适当的同步或只留下一个内容引用来处理。

EDIT2 - 读OP的评论

我刚才看了一下JSR 133 FAQ后 - AFAIU参考的安全出版物使用内存屏障不保证所提到的unsychronized领域引用的对象对象也可见。 final也不volatile

如果我没有曲解这个FAQ只同步一台监视器上定义了“之前发生”为关系的所有写操作释放同步锁被另一个线程获取同一显示器上的锁之前一个线程做。

我可能会误会,但它听起来好像也引用的对象的非同步字段将可见。

如果使用final关键字(如在你的例子,其中的参数被插入为final场) - 仅引用对象的实例字段本身是final保证对象的施工结束后可见。

但在BlockingQueue(并作为其实施LinkedBlockingQueue)我看到任何不​​关键字 - 它似乎使用了一些非常聪明的代码使用volatile领域实现同步,对我来说,听起来不像同步在JSR 133

描述意义上的监视器上这将意味着通过执行程序使用常见的阻塞队列不保证您的Unsafe情况下的非最终字段的可见性。虽然参考本身可以使用关键字final进行安全发布,但此参考指向的域的安全发布也要求字段为final,或者与作者和读者共享的监视器同步。

不要拍信使:-)。

3

您在第一个示例中留下了一些重要的代码:mExecutor对象可能拥有BlockingQueuemExecutor.execute(r)调用可能调用q.put(r)将您的任务添加到队列中,然后稍后,工作程序线程在调用r.run()之前调用r=q.take()以获取任务。

阻塞队列的put()take()方法将在会通过“安全发布”成语之一建立两个线程事件之间的关系“之前发生”建立的同类。

无论在调用q.put(r)之前第一个线程在内存中更新的内容是否保证在调用返回之前在第二个线程中都可见。

+0

您提出的观点我认为在这里忽略了一点:OP明确地谈论了非线程安全的对象,“发生之前”的语义有助于设置引用,但不会针对调用者*突变可变对象。* Runnable有已被放入队列并且可能与Runnable中正在使用的同一时间完全一致。所以实际上它在访问所讨论的局部变量方面确保没有任何线程安全和安全发布。 –

+0

@TomaszStanczak,OP似乎说这不会发生,“......只要发布时,创建该对象的线程就会放弃对该对象的引用,以便它不再干扰或不安全地观察该对象在另一个线程中的使用“。 –

+0

而这正是保证,而不是'put'和'get'之间的“发生之前”关系,而这本身并不能保证。其实已经有函数调用有这个关系,会发生什么是:1.调用publish,2.放,3. get。如果原始变量引用被丢弃,那么'publish'的调用就足够了,而不管接下来会发生什么。 –