栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

JUC学习 - java中的UnSafe类

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

JUC学习 - java中的UnSafe类

一、java中的UnSafe类 1、基本介绍

juc中大部分类都是依赖于Unsafe来实现的,主要用到了Unsafe中的CAS、线程挂起、线程恢复等相关功能。所以如果打算深入了解JUC原理的,必须先了解一下Unsafe类。

先上一幅Unsafe类的功能图:

Unsafe是位于sun.misc包下的一个类,主要提供一些用于执行低级别、不安全操作的方法,如直接访问系统内存资源、自主管理内存资源等,这些方法在提升Java运行效率、增强Java语言底层资源操作能力方面起到了很大的作用。

但由于Unsafe类使Java语言拥有了类似C语言指针一样操作内存空间的能力,这无疑也增加了程序发生相关指针问题的风险。

在程序中过度、不正确使用Unsafe类会使得程序出错的概率变大,使得Java这种安全的语言变得不再“安全”,因此对Unsafe的使用一定要慎重。

从Unsafe功能图上看出,Unsafe提供的API大致可分为内存操作、CAS、Class相关、对象操作、线程调度、系统信息获取、内存屏障、数组操作等几类,本文主要介绍3个常用的操作:CAS、线程调度、对象操作。

看一下Unsafe的源码部分:

public final class Unsafe {
  // 单例对象
  private static final Unsafe theUnsafe;
    
  private Unsafe() {
  }
    
  @CallerSensitive
  public static Unsafe getUnsafe() {
    Class var0 = Reflection.getCallerClass();
    // 仅在引导类加载器`BootstrapClassLoader`加载时才合法
    if(!VM.isSystemDomainLoader(var0.getClassLoader())) {    
      throw new SecurityException("Unsafe");
    } else {
      return theUnsafe;
    }
  }
    
}

从代码中可以看出,Unsafe类为单例实现,提供静态方法getUnsafe获取Unsafe实例,内部会判断当前调用者是否是由系统类加载器(BootstrapClassLoader)加载的,如果不是系统类加载器加载的,会抛出SecurityException异常。

  • 那我们想使用这个类,如何获取呢?

可以把我们的类放在jdk的lib目录下,那么启动的时候会自动加载,这种方式不是很好。

我们学过反射,通过反射可以获取到Unsafe中的theUnsafe字段的值,这样可以获取到Unsafe对象的实例。

2、通过反射获取Unsafe实例

代码如下:

public class GetUnsafeDemo {
    
    static Unsafe unsafe;
    static {
        try {
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            unsafe = (Unsafe) field.get(null);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public static void main(String[] args) {
        System.out.println(unsafe);
    }
    
}

运行上面的代码,输出结果:

sun.misc.Unsafe@2503dbd3
3、Unsafe中的CAS操作

看一下Unsafe中CAS相关方法定义:

public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object update);
public final native boolean compareAndSwapInt(Object o, long offset, int expected,int update);
public final native boolean compareAndSwapLong(Object o, long offset, long expected, long update);

什么是CAS? 即比较并替换,实现并发算法时常用到的一种技术。

CAS操作包含三个操作数——内存位置、预期原值及新值。执行CAS操作的时候,将内存位置的值与预期原值比较,如果相匹配,那么处理器会自动将该位置值更新为新值,否则,处理器不做任何操作,多个线程同时执行CAS操作,只有一个会成功。

我们都知道,CAS是一条CPU的原子指令(cmpxchg指令),不会造成所谓的数据不一致问题,Unsafe提供的CAS方法(如compareAndSwapXXX)底层实现即为CPU指令cmpxchg。

执行cmpxchg指令的时候,会判断当前系统是否为多核系统,如果是就给总线加锁,只有一个线程会对总线加锁成功,加锁成功之后会执行CAS操作,也就是说CAS的原子性实际上是CPU实现的, 其实在这一点上还是有排他锁的,只是比起用synchronized, 这里的排他时间要短的多, 所以在多线程情况下性能会比较好。

说一下offset,offset为字段的偏移量,每个对象有个地址,offset是字段相对于对象地址的偏移量,对象地址记为baseAddress,字段偏移量记为offset,那么字段对应的实际地址就是baseAddress+offset,所以CAS通过对象、偏移量就可以去操作字段对应的值了。

CAS在java.util.concurrent.atomic相关类、Java AQS、JUC中并发集合等实现上有非常广泛的应用,我们看一下java.util.concurrent.atomic.AtomicInteger类,这个类可以在多线程环境中对int类型的数据执行高效的原子修改操作,并保证数据的正确性,看一下此类中用到Unsafe CAS的地方:



JUC中其他地方使用到CAS的地方就不列举了,有兴趣的可以去看一下源码。【CAS的内容前面的博客中已经讲过了,这里就不详细展开了】

4、Unsafe中原子操作相关方法介绍

5个方法,看一下实现:

public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
        var5 = this.getIntVolatile(var1, var2);
    } while (!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
    return var5;
}


public final long getAndAddLong(Object var1, long var2, long var4) {
    long var6;
    do {
        var6 = this.getLongVolatile(var1, var2);
    } while (!this.compareAndSwapLong(var1, var2, var6, var6 + var4));
    return var6;
}


public final int getAndSetInt(Object var1, long var2, int var4) {
    int var5;
    do {
        var5 = this.getIntVolatile(var1, var2);
    } while (!this.compareAndSwapInt(var1, var2, var5, var4));
    return var5;
}


public final long getAndSetLong(Object var1, long var2, long var4) {
    long var6;
    do {
        var6 = this.getLongVolatile(var1, var2);
    } while (!this.compareAndSwapLong(var1, var2, var6, var4));
    return var6;
}


public final Object getAndSetObject(Object var1, long var2, Object var4) {
    Object var5;
    do {
        var5 = this.getObjectVolatile(var1, var2);
    } while (!this.compareAndSwapObject(var1, var2, var5, var4));
    return var5;
}

看一下上面的方法,内部通过自旋的CAS操作实现的,这些方法都可以保证操作的数据在多线程环境中的原子性,正确性。

来个示例,我们还是来实现一个网站计数功能,同时有100个人发起对网站的请求,每个人发起10次请求,每次请求算一次,最终结果是1000次,代码如下:

public class CountTheNumberOfRequest {

    static Unsafe unsafe;
    //用来记录网站访问量,每次访问+1
    static int count;
    //count在Demo.class对象中的地址偏移量
    static long countOffset;

    static {
        try {
            //获取Unsafe对象
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            unsafe = (Unsafe) field.get(null);
            Field countField = CountTheNumberOfRequest.class.getDeclaredField("count");
            //获取count字段在Demo2中的内存地址的偏移量
            countOffset = unsafe.staticFieldOffset(countField);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //模拟访问一次
    public static void request() throws InterruptedException {
        //模拟耗时5毫秒
        TimeUnit.MILLISECONDS.sleep(5);
        //对count原子加1
        unsafe.getAndAddInt(CountTheNumberOfRequest.class, countOffset, 1);
    }

    public static void main(String[] args) throws InterruptedException {
        long starTime = System.currentTimeMillis();
        int threadSize = 100;
        CountDownLatch countDownLatch = new CountDownLatch(threadSize);

        for (int i = 0; i < threadSize; i++) {
            Thread thread = new Thread(() -> {
                try {
                    for (int j = 0; j < 10; j++) {
                        request();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    countDownLatch.countDown();
                }
            });
            thread.start();
        }

        countDownLatch.await();

        long endTime = System.currentTimeMillis();
        System.out.println(Thread.currentThread().getName() + ",耗时:" + (endTime - starTime) + ",count=" + count);
    }

}

运行上面的代码,输出结果:

main,耗时:202,count=1000

代码中我们在静态块中通过反射获取到了Unsafe类的实例,然后获取CountTheNumberOfRequest中count字段内存地址偏移量countOffset,main方法中模拟了100个人,每人发起10次请求,等到所有请求完毕之后,输出count的结果。

代码中用到了CountDownLatch,通过countDownLatch.await()让主线程等待,等待100个子线程都执行完毕之后,主线程再继续运行。

5、Unsafe中线程调度相关方法

这部分,包括线程挂起、恢复、锁机制等方法。

//取消阻塞线程
public native void unpark(Object thread);
//阻塞线程,isAbsolute:是否是绝对时间,如果为true,time是一个绝对时间,如果为false,time是一个相对时间,time表示纳秒
public native void park(boolean isAbsolute, long time);
//获得对象锁(可重入锁)
@Deprecated
public native void monitorEnter(Object o);
//释放对象锁
@Deprecated
public native void monitorExit(Object o);
//尝试获取对象锁
@Deprecated
public native boolean tryMonitorEnter(Object o);

调用park后,线程将被阻塞,直到unpark调用或者超时,如果之前调用过unpark,不会进行阻塞,即park和unpark不区分先后顺序。monitorEnter、monitorExit、tryMonitorEnter 3个方法已过期,不建议使用了。

文章参考:http://www.itsoku.com/ 博主觉得这个文章的内容挺不错的,感兴趣的可以去了解一下。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/605506.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号