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

标签 java volatile bytebuffer unsafe java-memory-model

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

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

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

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

但它并没有真正解释这两个函数之间的区别。

我的理解是,对于 volatile ,它只在我们编写时才重要。对我来说,我们调用 putShortVolatile 应该是有意义的。然后为了阅读,我们可以简单地调用 getShort()由于 volatile write 已经保证新值已被刷新到主存储器中。

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

最佳答案

这里有一篇文章:http://mydailyjava.blogspot.it/2013/12/sunmiscunsafe.html
Unsafe 支持所有原始值,甚至可以通过使用方法的 volatile 形式写入值而不会影响线程本地缓存
getXXX(Object target, long offset):将从指定偏移处的目标地址读取 XXX 类型的值。
getXXXVolatile(Object target, long offset):将从指定偏移量处的目标地址读取 XXX 类型的值,并且不会命中任何线程本地缓存。
putXXX(Object target, long offset, XXX value):将值放在目标地址的指定偏移量处。
putXXXVolatile(Object target, long offset, XXX value):将值放置在目标地址的指定偏移量处,并且不会命中任何线程本地缓存。
更新:
您可以在这篇文章中找到有关内存管理和 volatile 字段的更多信息:http://cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html (它还包含一些重新排序的示例)。

In multiprocessor systems, processors generally have one or more layers of memory cache, which improves performance both by speeding access to data (because the data is closer to the processor) and reducing traffic on the shared memory bus (because many memory operations can be satisfied by local caches.) Memory caches can improve performance tremendously, but they present a host of new challenges. What, for example, happens when two processors examine the same memory location at the same time? Under what conditions will they see the same value?

Some processors exhibit a strong memory model, where all processors see exactly the same value for any given memory location at all times. Other processors exhibit a weaker memory model, where special instructions, called memory barriers, are required to flush or invalidate the local processor cache in order to see writes made by other processors or make writes by this processor visible to others.

The issue of when a write becomes visible to another thread is compounded by the compiler's reordering of code. If a compiler defers an operation, another thread will not see it until it is performed; this mirrors the effect of caching. Moreover, writes to memory can be moved earlier in a program; in this case, other threads might see a write before it actually "occurs" in the program.

Java includes several language constructs, including volatile, final, and synchronized, which are intended to help the programmer describe a program's concurrency requirements to the compiler. The Java Memory Model defines the behavior of volatile and synchronized, and, more importantly, ensures that a correctly synchronized Java program runs correctly on all processor architectures.


正如您在 What does volatile do? 部分中看到的那样

Volatile fields are special fields which are used for communicating state between threads. Each read of a volatile will see the last write to that volatile by any thread; in effect, they are designated by the programmer as fields for which it is never acceptable to see a "stale" value as a result of caching or reordering. The compiler and runtime are prohibited from allocating them in registers. They must also ensure that after they are written, they are flushed out of the cache to main memory, so they can immediately become visible to other threads. Similarly, before a volatile field is read, the cache must be invalidated so that the value in main memory, not the local processor cache, is the one seen.

There are also additional restrictions on reordering accesses to volatile variables. Accesses to volatile variables could not be reordered with each other. Is now no longer so easy to reorder normal field accesses around them. Writing to a volatile field has the same memory effect as a monitor release, and reading from a volatile field has the same memory effect as a monitor acquire. In effect, because the new memory model places stricter constraints on reordering of volatile field accesses with other field accesses, volatile or not, anything that was visible to thread A when it writes to volatile field f becomes visible to thread B when it reads f.


所以区别在于 setXXX() 和 getXXX() 可以重新排序,或者可以使用尚未在线程之间同步的缓存值,而 setXXXVolatile() 和 getXXXVolatile() 不会重新排序,将始终使用最后一个值.
线程本地缓存是java用于提高性能的临时存储:数据将在缓存中写入/读取/从缓存中刷新,然后刷新到内存中。
在单线程上下文中你可以同时使用非 volatile 和 volatile 版本的那些方法,不会有任何区别。当你写东西时,不管它是立即写在内存上还是只写在线程本地缓存中:当你尝试读取它时,你将在同一个线程中,所以你会得到最后一个确定的值(线程本地缓存包含最后一个值)。
相反,在多线程上下文中,缓存可能会给您带来一些麻烦。
如果你初始化一个不安全的对象,并且你在两个或多个线程之间共享它,那么每个线程都会在它的本地缓存中拥有一个副本(这两个线程可以在不同的处理器上运行,每个线程都有它的缓存)。
如果在线程上使用 setXXX() 方法,新值可能会写入线程本地缓存,但尚未写入内存。因此,可能只有多个线程中的一个包含新值,而内存和其他线程本地缓存包含旧值。这可能会带来意想不到的结果。 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)。

关于java - java unsafe中getXXXVolatile与getXXX有什么区别?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48615456/

相关文章:

java - 如何获取用于制作大字节数组的实际字节?

java - OpenGL ES Android - 未加载纹理(呈现黑色)

java - Gradle 多项目 - OpenClover

java - Spring MVC 请求映射 - 完整请求 URI

c - 包装函数的内联汇编器由于某种原因不起作用

Java - 将 16 位有符号 pcm 音频数据数组转换为 double 组

ubuntu - 安装 java - 我应该使用 .rpm 文件还是 .tar.gz?

C# 编译器优化和 volatile 关键字

C++ 静态数组初始化 : does inline initialization reserve space?

java - 将一定数量的字节读入 ByteBuffer