Unsafe类调用及自定义Atomic原子类

思维导图:

image-20220501120423636

unsafe类功能及使用请看:https://www.jianshu.com/p/db8dce09232d

Java和C++语言的一个重要区别就是Java中我们无法直接操作一块内存区域,不能像C++中那样可以自己申请内存和释放内存。Java中的Unsafe类为我们提供了类似C++手动管理内存的能力。
Unsafe类,全限定名是sun.misc.Unsafe,从名字中我们可以看出来这个类对普通程序员来说是“危险”的,一般应用开发者不会用到这个类。

1. Unsafe类调用

错误方案

原因:在调用Unsafe.getUnsafe()时会判断调用类的父级类加载器,只有是根加载器才能调用,即我们无法调用此方法拿到一个Unsafe实例

/**
 * 需求:通过UnSafe类提供的compareAndSwapInt()方法来完成一个类中的state值的原子性操作
 *  1. 创建Unsafe对象,Unsafe构造方法是私有的,可以尝试通过public static Unsafe getUnsafe()
 *  2. 要通过 public final native boolean compareAndSwapLong(Object o, long offset,
 *                                                long expected,
 *                                                long x);来设置值,但问题是offset如何设置
 *  3. 通过 public native long objectFieldOffset(Field var1);来获取一个属性的偏移地址
 */
public class Test1_unsafe_error {
    /**
     * 方案一:错误方案,因为Test1_unsafe是用AppClassLoader加载的,而Unsafe类是在rt.jar下,它由BootStrap类加载器加载
     */
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    static long stateOffset;//state变量在内存中的偏移量
    private volatile int state = 0; //要设置的值
    static{
        try {
        	//错误方案                                      这是反射中的filed对象,这里值的是state这个属性
            stateOffset = unsafe.objectFieldOffset(Test1_unsafe_error.class.getDeclaredField("state"));
        } catch (Exception e) {
        	e.printStackTrace();
        }
    }
    public static void main(String[] args) {
        Test1_unsafe_error test = new Test1_unsafe_error();
        //通过设置内存偏移量来操作state的值        要操作的对象    要操作属性的偏移量   原值   要更新的值
        boolean flag = unsafe.compareAndSwapInt(test,        stateOffset, 0, 1);
        System.out.println("设置结果:"+flag);
        //输出修改后的值
        System.out.println(test.state);
    }
}

我们看到unsafe类的构造器是私有化的,也就是说我们不能通过私有的构造器new出来

image-20220502185327270

那既然你们已经有一个实例了,我们能不能通过静态方法private static Unsafe unsafe = Unsafe.getUnsafe()直接拿到呢?

其实是不能的,我们调用 Unsafe.getUnsafe()时,会执行以下代码

@CallerSensitive
public static Unsafe getUnsafe() {
    Class<?> caller = Reflection.getCallerClass();
    if (!VM.isSystemDomainLoader(caller.getClassLoader()))
        throw new SecurityException("Unsafe");
    return theUnsafe;
}

点进VM.isSystemDomainLoader方法中我们就能看到

public static boolean isSystemDomainLoader(ClassLoader loader) {
    return loader == null;
}

显然只有在rt.jar包下的类才会用根加载器加载,其调用getClassLoader()取到的加载器为null,才能拿到unsafe对象

正确方案

public class Test1_unsafe_error {
    /**
     * 方案一:错误方案,因为Test1_unsafe是用AppClassLoader加载的,而Unsafe类是在rt.jar下,它由BootStrap类加载器加载
     */
//    private static Unsafe unsafe = Unsafe.getUnsafe();
    static long stateOffset;//state变量在内存中的偏移量
    //方案二,正确方案
    static Unsafe unsafe;
    private volatile int state = 0; //要设置的值
    static{
        try {

            //方案二,正确方案
            //使用反射获取Unsafe的成员变量theUnsafe
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            //设置访问权限
            field.setAccessible(true);
            //获取此变量的值
            unsafe = (Unsafe)field.get(null);
            //获取state变量在Test1_unsafe中的偏移量
            stateOffset = unsafe.objectFieldOffset(Test1_unsafe_error.class.getDeclaredField("state"));

        	//方案一,错误方案                                      这是反射中的filed对象,这里值的是state这个属性
//            stateOffset = unsafe.objectFieldOffset(Test1_unsafe_error.class.getDeclaredField("state"));
        } catch (Exception e) {
        	e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Test1_unsafe_error test = new Test1_unsafe_error();
        //通过设置内存偏移量来操作state的值        要操作的对象    要操作属性的偏移量   原值   要更新的值
        boolean flag = unsafe.compareAndSwapInt(test,        stateOffset, 0, 1);
        System.out.println("设置结果:"+flag);
        //输出修改后的值
        System.out.println(test.state);
    }
}

2. 自定义Atomic原子类

我们先来看一下Atomic原子类的基本API操作

public class Test2_AtomicInteger {
    public static void main(String[] args) {
        AtomicInteger a = new AtomicInteger(10);
        System.out.println(a.getAndIncrement());//安全的自增
        System.out.println(a.incrementAndGet());//先增加在get
        System.out.println(a.get());
        System.out.println(a.addAndGet(2));//增加2再get
        System.out.println(a.getAndSet(2));//拿到再设置值
        System.out.println(a.get());
    }
}

输出结果:

10
12
12
14
14
2

我们知道Atomic原子类能够保证原子性,接下来看一下使用:

public class Test2_AtomicInteger_count {
    private static AtomicLong a = new AtomicLong();
    private static Integer[] arr1 = {0, 1, 2, 3, 4, 0, 6, 7, 8, 0};
    private static Integer[] arr2 = {0, 1, 2, 3, 4, 0, 6, 7, 8, 0};

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i : arr1) {
                if (i == 0) {
                    a.incrementAndGet();
                }
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i : arr2) {
                if (i == 0) {
                    a.incrementAndGet();
                }
            }
        });
        t1.start();
        t2.start();
        t1.join(); //防止子线程还没有运行完,主线程先运行完了
        t2.join();
        System.out.println("0出现了:" + a.get() + "次");
    }
}
输出结果:
    0出现了:6

我们知道i++并不是一个原子操作,因为同时涉及到了内存读和内存写,但是我们使用Atomic原子类就能保证该操作为原子操作,底层其实就是调用了unsafe

public final long incrementAndGet() {
    return unsafe.getAndAddLong(this, valueOffset, 1L) + 1L;
}

unsafe底层调用了CAS进行硬件级别的并发保证

public final long getAndAddLong(Object o, long offset, long delta) {
    long v;
    do {
        v = getLongVolatile(o, offset);
    } while (!compareAndSwapLong(o, offset, v, v + delta));
    return v;
}

接下来我们也使用unsafe类自定义一个简单的Atomic原子类

public class Test3_myAtomicInteger {
    private static MyAtomicInteger a = new MyAtomicInteger();
    private static Integer[] arr1 = {0, 1, 2, 3, 4, 0, 6, 7, 8, 0};
    private static Integer[] arr2 = {0, 1, 2, 3, 4, 0, 6, 7, 8, 0};

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i : arr1) {
                if (i == 0) {
                    a.incrementAndGet();
                }
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i : arr2) {
                if (i == 0) {
                    a.incrementAndGet();
                }
            }
        });
        t1.start();
        t2.start();
        t1.join(); //防止子线程还没有运行完,主线程先运行完了
        t2.join();
        System.out.println("0出现了:" + a.get() + "次");
    }
}

class MyAtomicInteger {
    private static Unsafe unsafe;
    private static long stateOffset;//state变量在内存中的偏移量
    private volatile long state = 0;//要设置的值

    static {
        try {
            //通过反射拿到Unsafe类的实例
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            //设置访问权限
            field.setAccessible(true);
            //获取此变量的值
            unsafe = (Unsafe) field.get(null);
            //获取state变量在当前类中的偏移量
            stateOffset = unsafe.objectFieldOffset(MyAtomicInteger.class.getDeclaredField("state"));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public final long incrementAndGet() {
        int v;
        do {
            v = unsafe.getIntVolatile(this, stateOffset);
            System.out.println(Thread.currentThread().getName() + "值 为:" + v);
        } while (!unsafe.compareAndSwapInt(this, stateOffset, v, v + 1));
        return v;
    }

    public long get() {
        return unsafe.getInt(this, stateOffset);
    }
}

输出结果:

Thread-0值 为:0
Thread-1值 为:0
Thread-0值 为:1
Thread-1值 为:1
Thread-0值 为:2
Thread-1值 为:2
Thread-1值 为:3
Thread-1值 为:4
Thread-1值 为:5
0出现了:6

可以看到如果在单线程情况下应该运行6次,但是这里却运行了9次,在这里就出现了3次冲突,自旋锁起作用了!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值