java unsafe中getXXXVolatile与getXXX有什么区别?

时间:2018-02-05 03:23:01

标签: java volatile bytebuffer unsafe java-memory-model

我试图理解java unsafe中的两种方法:

   public native short getShortVolatile(Object var1, long var2);

VS

   public native short getShort(Object var1, long var2);

这里有什么真正的区别?这里的挥发性真正起作用了什么?我在这里找到了API文档:http://www.docjar.com/docs/api/sun/misc/Unsafe.html#getShortVolatile(Object,%20long)

但它并没有真正解释两种功能之间的区别。

我的理解是,对于不稳定的,只有在我们写作时才有意义。对我来说,我们调用putShortVolatile然后进行读取应该是有意义的,我们可以简单地调用getShort(),因为volatile写入已经保证新值已经被刷新到主存储器中。

如果有任何问题,请帮我纠正。谢谢!

2 个答案:

答案 0 :(得分:5)

这里有一篇文章:http://mydailyjava.blogspot.it/2013/12/sunmiscunsafe.html

Unsafe支持所有原始值,甚至可以通过使用方法的volatile形式来编写值而无需访问线程局部缓存

getXXX(对象目标,长偏移):将从指定偏移处的目标地址读取类型XXX的值。

getXXXVolatile(Object target,long offset):将从指定偏移量的目标地址读取类型XXX的值,而不是命中任何线程本地缓存。

putXXX(对象目标,长偏移,XXX值):将值放在指定偏移量的目标地址上。

putXXXVolatile(对象目标,长偏移量,XXX值):将值放在指定偏移量的目标地址处,而不是命中任何线程本地缓存。

UPDATE:

您可以在本文中找到有关内存管理和易失性字段的更多信息:http://cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html(它还包含一些重新排序示例)。

  

在多处理器系统中,处理器通常具有一层或多层内存缓存,通过加快对数据的访问(因为数据更靠近处理器)和减少共享内存总线上的流量(因为许多内存操作)来提高性能可以通过本地缓存来满足。)内存缓存可以极大地提高性能,但它们带来了许多新的挑战。例如,当两个处理器同时检查相同的内存位置时会发生什么?在什么条件下他们会看到相同的价值?

     

某些处理器表现出强大的内存模型,其中所有处理器始终看到任何给定内存位置的完全相同的值。其他处理器表现出较弱的内存模型,需要特殊指令(称为内存屏障)来刷新或使本地处理器高速缓存无效,以便查看其他处理器进行的写入或使该处理器的写入对其他处理器可见。

     

编译器重新排序代码时,写入对另一个线程可见的问题更加复杂。如果编译器推迟操作,则另一个线程在执行之前不会看到它;这反映了缓存的效果。此外,可以在程序中更早地移动对存储器的写入;在这种情况下,其他线程可能会在它实际发生之前看到写入"在该计划中。

     

Java包括几种语言结构,包括volatile,final和synchronized,它们旨在帮助程序员描述程序对编译器的并发要求。 Java内存模型定义了volatile和synchronized的行为,更重要的是,确保正确同步的Java程序在所有处理器体系结构上正确运行。

正如您在What does volatile do?

部分所见
  

易失性字段是用于在线程之间传递状态的特殊字段。每次读取volatile都会看到任何线程对该volatile的最后一次写入;实际上,它们被程序员指定为一个字段,对于这些字段来说,它们永远不会被接受来看待#34; stale"缓存或重新排序的结果。禁止编译器和运行时将它们分配到寄存器中。他们还必须确保在写入之后,将它们从缓存中刷新到主内存,这样它们就可以立即对其他线程可见。类似地,在读取volatile字段之前,必须使高速缓存无效,以便主存储器中的值(而不是本地处理器高速缓存)是所见的值。

     

对重新排序对volatile变量的访问还有其他限制。对volatile变量的访问不能相互重新排序。现在不再那么容易重新排序它们周围的正常字段访问。写入易失性字段与监视器释放具有相同的记忆效应,从易失性字段读取具有与监视器获取相同的记忆效应。实际上,因为新的存储器模型对具有其他字段访问(易失性或非易失性)的易失性字段访问的重新排序施加了更严格的约束,所以当线程A写入易失性字段f时线程A可见的任何内容在读取f时对线程B可见。

所以区别在于setXXX()和getXXX()可以被重新排序,或者可以使用尚未在线程之间同步的缓存值,而setXXXVolatile()和getXXXVolatile()不会被重新排序,并且始终使用最后一个值。

线程本地缓存是一个用于提高性能的临时存储:数据将被写入/读入缓存,然后才能在内存中刷新。

在单个线程上下文中,您可以同时使用那些方法的非易失性而非易失性版本,没有区别。当你写一些东西时,如果它是立即写在内存上或只是在线程本地缓存中并不重要:当你试图阅读它时,你将会在同一个线程中,所以你肯定会得到最后一个值(线程本地缓存包含最后一个值)。

在多线程上下文中,缓存可能会给您带来一些麻烦。 如果你初始化一个不安全的对象,并在两个或多个线程之间共享它,那么每个线程都会将它的副本放入其本地缓存中(这两个线程可以在不同的处理器上运行,每个线程都有自己的缓存)。

如果在线程上使用setXXX()方法,则新值可以写入线程本地缓存中,但尚未写入内存中。因此,可能只有多个线程中的一个包含新值,而内存和其他threadds本地缓存包含旧值。这可能会带来意想不到的结果。 setXXXVolatile()方法将直接在内存中写入新值,因此其他线程也可以访问新值(如果它们使用getXXXVolatile()方法)。

如果使用getXXX()方法,则会获得本地缓存值。因此,如果另一个线程更改了内存中的值,则当前线程本地缓存仍可能包含旧值,并且您将获得未完成的结果。如果您使用getXXXVolatile()方法,则可以直接访问内存,并确保获得最后一个值。

使用上一个链接的示例:

class DirectIntArray {

  private final static long INT_SIZE_IN_BYTES = 4;

  private final long startIndex;

  public DirectIntArray(long size) {
    startIndex = unsafe.allocateMemory(size * INT_SIZE_IN_BYTES);
    unsafe.setMemory(startIndex, size * INT_SIZE_IN_BYTES, (byte) 0);
    }
  }

  public void setValue(long index, int value) {
    unsafe.putInt(index(index), value);
  }

  public int getValue(long index) {
    return unsafe.getInt(index(index));
  }

  private long index(long offset) {
    return startIndex + offset * INT_SIZE_IN_BYTES;
  }

  public void destroy() {
    unsafe.freeMemory(startIndex);
  }
}

此类使用putInt和getInt将值写入内存中分配的数组(因此在堆空间之外)。 如前所述,这些方法将数据写入线程本地缓存中,而不是立即存储在内存中。因此,当您使用setValue()方法时,本地缓存将立即更新,分配的内存将在一段时间后更新(它取决于JVM实现)。 在单个线程上下文中,该类可以正常工作。 在多线程上下文中,它可能会失败。

DirectIntArray directIntArray = new DirectIntArray(maximum);
Runnable t1 = new MyThread(directIntArray);
Runnable t2 = new MyThread(directIntArray);
new Thread(t1).start();
new Thread(t2).start();

MyThread的位置:

public class MyThread implements Runnable {
    DirectIntArray directIntArray;

    public MyThread(DirectIntArray parameter) {
        directIntArray = parameter;
    }

    public void run() {
        call();
    }

    public void call() {
        synchronized (this) {
            assertEquals(0, directIntArray.getValue(0L));  //the other threads could have changed that value, this assert will fails if the local thread cache is already updated, will pass otherwise
            directIntArray.setValue(0L, 10);
            assertEquals(10, directIntArray.getValue(0L));
        }
    }
}

使用putIntVolatile()和getIntVolatile(),两个线程中的一个肯定会失败(第二个线程将获得10而不是0)。 使用putInt()和getInt(),两个线程都可以成功完成(因为如果没有刷新写入器高速缓存或读取器高速缓存没有刷新,则两个线程的本地高速缓存仍然可以包含0 )。

答案 1 :(得分:1)

认为 getShortVolatile正在从对象中读取 plain ,但将其视为volatile;它就像读取一个普通变量并自己插入所需的障碍(如果有的话)。

大大简化(在某种程度上错了,但只是为了得到这个想法)。发布/获取语义:

Unsafe.weakCompareAndSetIntAcquire // Acquire
update some int here
Unsafe.weakCompareAndSetIntRelease // Release

至于为什么需要这个(这是getIntVolatile,但案件仍然有效),可能强制执行非重新排序。同样,这有点超出我和Gil Tene explaining this is FAR more suited