Java高并发11-伪共享,getUnsafe源码解析并利用反射获取Unsafe实例

一、复习

  • public native long getLongvolatile(Object obj,long offset)
  • public native long putLongvolatile(Object obj,long offset,long value)
  • void putOrderedLong(Object obj,long offset,long value)
  • void park(boolean isAbsolute,long time)
  • void unpark(Thread thread)
  • long getAndSwapLong(Object obj,long offset,long update)
  • long getAndAddLong(Object obj,long offset,long addValue)
  • Unsafe使用静态方法getUnsafe()举例

二、getUnsafe源码解析

private static final Unsafe unsafe = new Unsafe();
public static Unsafe getUsafe(){
 Class localClass = Reflection.getCallerClass();
 if(!VM.isSystemDomainLoader(localClass.getClassLoader())){
  throw new SecurityException("Unsafe");
 }
 return theUnsafe;
}
public static boolean isSystemDomainLoader(ClassLoader paramClassLoader){
 return paramClassLoader == null;
}
  • 首先调用了getUnsafe这个方法的对象的Class对象,这里就是TestUnsafe.class
  • 然后判断是不是Bootstrap类加载器的加载的localClass,在这里是看是不是Bootstrap加载器加载了TestUnsafe.class.很明显由于TestUnSafe.class是使用AppClassLoader加载的,所以这里直接抛出了异常。
  • 为什么需要这个判断?
  • 我们知道Unsafe类是rt.jar包提供的,rt.jar包里面的类是使用Bootstrap类加载器加载的,所以在main函数中加载Unsafe类的时候,根据委托机制,会委托给Bootstrap去加载Unsafe类。如果没有这个判断,那么我们的应用程序就可以随意使用Unsafe做事情了,而Unsafe类可以直接操作内存,这是不安全的。(想一想native方法)。所以JDK开发组特意做了这个判断,让开发人员在正规渠道使用Unsafe类,而是在rt.jar包里面的核心类中使用Unsafe功能。

三、使用万能的反射来获取Unsafe实例

package com.ruigege.OtherFoundationOfConcurrent2;

import java.lang.reflect.Field;

import sun.misc.Unsafe;
//import jdk.internal.misc.Unsafe;

public class TestUnsafe2 {
 static final Unsafe unsafe;
 static final long offset;
 private volatile long state=0;
 static {
  try {
   //使用反射获取成员变量theState的值
   Field field = Unsafe.class.getDeclaredField("unsafe");
   //设置域值为可存取
   field.setAccessible(true);
   //获取该变量field的值
   Unsafe unsafe = (Unsafe)field.get(null);
   //获得的这个unsafe类,我们使用它的方法来获取这个测试类的state变量的偏移量
   offset = unsafe.objectFieldOffset(TestUnsafe2.class.getDeclaredField("state"));
   
  }catch(Exception e) {
   e.printStackTrace();
   throw new Error(e);
  }
 }
 
 public static void main(String[] args) {
  TestUnsafe2 testUnsafe2 = new TestUnsafe2();
  boolean success = unsafe.compareAndSwapInt(testUnsafe2,offset,0,1);
  System.out.println(success);
 }
}
  • 如上基本在注释中都解释清楚了。

四、Java指令重排

  • Java在会对指令重新排列,如果两个语句之间没有依赖,那么执行哪个在先都行,这种机制在单线程是没有问题的,但是在多线程中会出现问题,他们之间的值相互依赖,就会出现错误的情况
  • 对变量是volatile,会避免重排序和内存可见性问题。
  • 写volatile变量时,可以保证volatile写之前的操作不会被编译器重排序到volatile写之后。读取volatile变量的时候,可以确保volatile读之后的操作不会被编译器重新排序到volatile读之前

五、伪共享

1.什么是cache

  • 在CPU内部会有一二级缓存,它们的存在是因为比直接读取内存更加快捷

2.cache内部如何存储

  • cache内部是按行存储的,其中每一行称为一个cache行。当程序访问变量的时候,会首先去缓存内寻找变量,如果没有找到,那么就去内存中找到这个变量并且把变量的缓存行大小的内存内容都会拿来放到缓存行中。

3.什么叫伪共享

  • 一个线程是按照cache行来访问的,因此如果多个变量放到了同一个cache行,并且此时多个线程访问这行里的不同变量,因此线程只能排队修改或取得,我们称之为伪共享。

4.出现的问题

  • CPU1里一级缓存同一cache行有x,y两个变量;CPU2里一级缓存同一cache行也有x,y两个变量
  • 此时CPU1修改了x,因此CPU2根据缓存一致性原则,自己一级缓存x失效,y也跟着访问不了,因此CPU2无论修改x还是y都需要去内存重新获取。
  • 这说明了多个线程不可能同时去修改自己所使用的CPU中相同缓存行里面的变量。

5.举个例子

int[][] a = new int[1000][1000];
for(int i=0;i<1000;i++){
 for(int j=0;j<1000;j++){
  a[i][j]=i+j;
 }
}

int[][] b = new int[1000][1000];
for(int i=0;i<1000;i++){
 for(int j=0;j<1000;j++){
  b[j][i]=i+j;
 }
}
  • 这两个循环赋值,上面的会明显快于下面的,因为数组就是按行存储,上面的就是一行一行赋值的,下面的按列赋值,因此每一个赋值,cache都得获取一个cache行,不同的行来回切换,因此会慢一些

六 、源码:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章