Java/JUC进阶/Java 并发 - 01 Unsafe

在 JUC 篇章中我们去了解了 JUC 相关的包结构和相关工具类锁等相关的概念,JUC 进阶篇章将深入去讲解相关的Java并发涉及的相关知识,以及涉及到的相关引用。

简介

Java不能直接访问操作系统底层,而是通过本地方法来访问。Unsafe类提供了硬件级别的原子操作, 为我们提供了访问底层的机制, 这种机制仅供Java 核心类库使用, 而不应该被普通用户使用。

所在包

package sun.misc;

Unsafe 的实例

查看Unsafe 的源码我们会发现它提供了一个 getUnsafe() 的静态方法

 @CallerSensitive
    public static Unsafe getUnsafe() {
        Class var0 = Reflection.getCallerClass();
        if (var0.getClassLoader() != null) {
            throw new SecurityException("Unsafe");
        } else {
            return theUnsafe;
        }
    }

直接调用这个方法会报一个SecurityException 异常, 这是因为Unsafe 仅给java 内部类使用,外部类不应该使用它。

但是Java 后门 反射可以达到我们使用此方法的目的

Unsafe 类中有个常量属性  theUnsafe

private static final Unsafe theUnsafe;

通过反射机制得到 unsafe 对象 

public class UnsafeTest {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        Field f = Unsafe.class.getDeclaredField("theUnsafe");
        f.setAccessible(true);
        Unsafe unsafe = (Unsafe) f.get(null);
    }
}

 使用Unsafe 实例化一个类

我们有一个类如下

class Student{
        int age ;
        Student(){
            this.age = 10 ;
        }
    }

正常情况下我们通过构造方法实例化这个类, age 属性返回是 10 

package com.suning.test;

/**
 * @Author wangli
 * @Descrintion:
 * @Date : Created in 11:47 2019/5/7
 * @
 */
public class Student {

    int age ;
    Student(){
        this.age = 10 ;
    }

    public static void main(String[] args) {
        Student s = new Student();
        System.out.println(s.age);
    }

}

如果我们使用  Unsafe来进行实例化 结果会是什么呢

package com.suning.test;

import sun.misc.Unsafe;

import java.lang.reflect.Field;

/**
 * @Author wangli
 * @Descrintion:
 * @Date : Created in 11:47 2019/5/7
 * @
 */
public class Student {

    int age ;
    Student(){
        this.age = 10 ;
    }

    public static void main(String[] args)throws Exception {
        Field f = Unsafe.class.getDeclaredField("theUnsafe");
        f.setAccessible(true);
        Unsafe unsafe = (Unsafe) f.get(null);
        Student user = (Student)unsafe.allocateInstance(Student.class);
        System.out.println(user.age);
    }

}

根据结果我们看到输出的 0 ,这是因为  Unsafe.allocateInstance()  只会给对象分配内存, 并不会调用构造方法,所以输出int类型默认值 0 。

修改私有字段的值

通过 Unsafe 的putXXXX() 方法,我们可以修改任意私有字段的值

package com.suning.test;

import sun.misc.Unsafe;

import java.lang.reflect.Field;

/**
 * @Author wangli
 * @Descrintion:
 * @Date : Created in 11:47 2019/5/7
 * @
 */
public class Student {

    private int age ;
    Student(){
        this.age = 10 ;
    }

    public int getAge() {
        return age;
    }

    public static void main(String[] args)throws Exception {
        Field f = Unsafe.class.getDeclaredField("theUnsafe");
        f.setAccessible(true);
        Unsafe unsafe = (Unsafe) f.get(null);

        Student student = new Student();
        Field age = student.getClass().getDeclaredField("age");
        unsafe.putInt(student, unsafe.objectFieldOffset(age), 20);
        System.out.println(student.getAge());
    }

}

通过反射调用得到字段 age , 我们就可以使用Unsafe 将其值更改为任何其他 int 值,也可以通过反射直接修改

Unsafe 异常处理

一般情况下,我们有两种方式来抛异常

1. throws  Exception 

2. throw  new Exception

通过 unsafe 方式不需要进行 try{}catch(){} 进行捕捉

   public static void readFile()throws IOException{
        throw new IOException();
    }
    
    public static void readFileUnsafe(){
        unsafe.throwException(new IOException());
    }

堆外内存的使用

我们都知道 Java 对象一般都是使用JVM 的堆内存来存储信息, 当内存不足时就会发生GC ,来释放内存,来给其他对象使用。

另外我们也可以考虑堆外内存来存储信息,当然堆外内存是不受 JVM 管理的,所以我们必须手动 allocate(分配)  和  free(释放)

假设我们要在堆外创建一个巨大的int 数组, 我们可以使用 allocateMemory() 方法来实现:

package com.suning.test;

import sun.misc.Unsafe;

import java.lang.reflect.Field;

/**
 *
 * @Author wangli 
 * @Descrintion:
 * @Date : Created in 14:59 2019/5/7
 * @
 */
public class OffHeapArray {

    private static int INT = 4 ;
    private long size ;
    private long address ;

    private static Unsafe unsafe ;
    static {
        try {
            Field f = Unsafe.class.getDeclaredField("theUnsafe");
            f.setAccessible(true);
            unsafe = (Unsafe)f.get(null);
        }catch (Exception e){

        }
    }

    public OffHeapArray(long size){
        this.size = size;
        address = unsafe.allocateMemory(size * INT);
    }

    public int get(long i){
        return unsafe.getInt(address + i * INT);
    }

    public void set(long i, int value){
        unsafe.putInt(address + i * INT, value);
    }

    public long size(){
        return size ;
    }

    public void freeMemory(){
        unsafe.freeMemory(address);
    }

    public static void main(String[] args) {
        OffHeapArray offHeapArray = new OffHeapArray(5);
        offHeapArray.set(0, 1);
        offHeapArray.set(1, 2);
        offHeapArray.set(2, 3);
        offHeapArray.set(3, 4);
        offHeapArray.set(2, 5); // 在索引2的位置重复放入元素

        int sum = 0;
        for (int i = 0; i < offHeapArray.size(); i++) {
            sum += offHeapArray.get(i);
        }
        // 打印12
        System.out.println(sum);

        offHeapArray.freeMemory();
    }

}

 CAS(CompareAndSwap)底层方法

JUC 下面大量使用了 CAS 操作,它们底层是调用了 Unsafe 的 CompareAndSwapXXX() 方法。这种方式广泛运用于无锁算法, 与java 中标准的悲观锁机制相比, 它可以利用CAS 处理器指令提供极大的加速。

基于Unsafe 的compareAndSwapInt() 方法构建线程安全的计数器

我们定义了一个volatile 的字段count , 对所有线程可见,并在类加载时获取 count 在类中的偏移地址。

在 increment() 方法中,我们通过调用Unsafe 的compareAndSwapInt() 来尝试更新之前获取到的count 值, 如果没有被其他线程更新,则更新成功 , 否则不断尝试更新直到成功。

package com.test;

import sun.misc.Unsafe;

import java.lang.reflect.Field;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.IntStream;

/**
 *
 * @Author wangli 
 * @Descrintion:
 * @Date : Created in 16:11 2019/5/7
 * @
 */
public class Counter {

    private volatile  int count = 0 ;
    private static long offset ;
    private static Unsafe unsafe;
    static{
        try{
            Field f = Unsafe.class.getDeclaredField("theUnsafe");
            f.setAccessible(true);
            unsafe = (Unsafe) f.get(null);
            offset = unsafe.objectFieldOffset(Counter.class.getDeclaredField("count"));
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public void increment(){
        int before = count ;
        while (!unsafe.compareAndSwapInt(this,offset,before,before + 1)){ //自旋
            before = count ;
        }
    }

    public int getCount(){
        return count ;
    }


    public static void main(String[] args) throws Exception{
        Counter counter = new Counter();
        ExecutorService threadPool = Executors.newFixedThreadPool(100);

        // 起100个线程,每个线程自增10000次
        IntStream.range(0, 100)
                .forEach(i->threadPool.submit(()->IntStream.range(0, 10000)
                        .forEach(j->counter.increment())));

        threadPool.shutdown();

        Thread.sleep(2000);

        // 打印1000000
        System.out.println(counter.getCount());
    }

}

park / unpark 

JVM 在上下文切换的时候使用了 Unsafe 中两个非常牛逼的方法 park() 和 unpark()

当一个线程正在等待某个操作时,JVM 调用 Unsafe 的 park() 方法来阻塞此线程

当阻塞中的线程需要再次运行时, JVM 调用 Unsafe 的 unpark() 方法来唤醒此线程。

LockSupport.park()/unpark() 底层就是调用的Unsafe 的方法。

猜你喜欢

转载自blog.csdn.net/wszhongguolujun/article/details/89877446