jdk源码解读-原子类和Unsafe类

原子类

jdk中的原子类是在java.util.concurrent.atomic包下的,常用的就是AtomicInteger,AtomicBoolean,AtomicLong这几个,包下也包括数组、对象的原子类,这些原子类的实现都基本相似,以AtomicInteger为例,主要需要关注两个点:

  1. 数据存储的value是volatile修饰的。

    private volatile int value;
    

    这里先说一下volatile关键字,众所周知,volatile关键字有两个作用,修饰的变量对所有线程可见和禁止指令重排。对于volatile修饰的变量,只能保证对其他线程的可见性,但不能保证变量操作的原子性,例如线程A,B获取变量a,两个线程获取到的a一定是一样的,但如果对a进行加1操作,应该最终结果为3,但可能为2。

  2. 原子操作都是基于Unsafe类的CAS来实现的

这里自己写了一个实验例子,让多个线程去对自增一个Integer,最后看这个数的最终值是否符合预期。实验目的有三点:

  1. 验证volatile关键字并不具备原子的特性
  2. 通过Unsafe的API来实现CAS
  3. 通过对Integer++自增操作加锁synchronized,来证明保证了操作的原子性
public class Main {
    private volatile static Integer num1;
    private volatile static Integer num2;
    private static Integer num3;
    private static final int c = 100;

    public static void main(String[] args) throws Throwable {
        ArrayList<Object> list1 = new ArrayList<>();
        ArrayList<Object> list2 = new ArrayList<>();
        ArrayList<Object> list3 = new ArrayList<>();

        for (int j = 0; j < 10; j++) {
            num1 = 300;
            num2 = 300;
            num3 = 300;

            //没有cas
            CountDownLatch t1 = new CountDownLatch(c);
            for (int i = c; i > 0; i--) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        num1++;
                        t1.countDown();
                    }
                }).start();
            }
            t1.await();
            list1.add(num1);

            //cas
            Field f = Unsafe.class.getDeclaredField("theUnsafe");
            f.setAccessible(true);
            Unsafe unsafe = (Unsafe)f.get(null);
            Long valueOffset = unsafe.objectFieldOffset
                (Integer.class.getDeclaredField("value"));
            CountDownLatch t2 = new CountDownLatch(c);
            for (int i = c; i > 0; i--) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        int intVolatile;
                        do {
                            intVolatile = unsafe.getIntVolatile(num2, valueOffset);
                        } while (!unsafe.compareAndSwapInt(num2, valueOffset, intVolatile, intVolatile + 1));
                        //compareAndSwapInt(哪个对象,对象中的偏移量,初始值,期望值)
                        t2.countDown();
                    }
                }).start();
            }
            t2.await();
            list2.add(num2);

            //synchronized
            CountDownLatch t3 = new CountDownLatch(c);
            for (int i = c; i > 0; i--) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        synchronized (num3) {
                            num3++;
                        }
                        t3.countDown();
                    }
                }).start();
            }
            t3.await();
            list3.add(num2);
        }
        System.out.println(list1);
        System.out.println(list2);
        System.out.println(list3);
    }

}

最后结果为:

[399, 400, 397, 400, 397, 400, 399, 397, 397, 399]
[400, 400, 400, 400, 400, 400, 400, 400, 400, 400]
[400, 400, 400, 400, 400, 400, 400, 400, 400, 400]

这里选用num的值为300是由于Integer类在装箱的时候存在integerCache缓存的原因,Integer装箱的时候会调用valueOf方法,valueOf的源码如下:

    public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }

可以看到这里会对输入参数i进行判断,如果i在IntegerCache的范围内(默认-128~127,可以通过vm启动参数修改),就不会创建新的Integer实例。这里如果选择num=0,对其进行num++操作,那么改变的永远是num的指针,也就是随着num++操作,num指向的地址在不断变化,不符合试验设计需要,并且num=0初始化赋值的操作将没有意义。下图中可以看出来,变化的只是num的指针地址
在这里插入图片描述
在这里插入图片描述

Unsafe类

Unsafe类是在sun.misc包下的,并不是java推荐的使用API,因为正如同名字一样,使用这个类很有可能带来问题,但这个类确在Netty等一些框架中广泛使用,这是因为Unsafe类提供了直接操作底层计算机的方法,可以极大提升性能

1. 构造方法

private Unsafe() {
}

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

unsafe类是典型的单例模式建设的类,但并不能通过静态方法getUnsafe来获取到unsafe的实例,如果是普通的调用的话,它会抛出一个SecurityException异常;只有由主类加载器加载的类才能调用这个方法,可以通过反射的方式去获取,例如实验例子中获取Unsafe实例的方式。

Field f = Unsafe.class.getDeclaredField("theUnsafe");
            f.setAccessible(true);
            Unsafe unsafe = (Unsafe)f.get(null);

2. Unsafe类的主要使用场景

再说一下Unsafe类,这个类提供了一些直接操作内存,线程等的方法,但在日常开发中不会使用到Unsafe类,也不推荐使用,但在一些底层的操作,例如Netty、kafka等都使用了Unsafe类,这里对这个类的使用场景(能做的事情)进行一下简单的介绍。

  1. 直接操作内存(基本都是native方法)
    该部分包括了allocateMemory(分配内存)、reallocateMemory(重新分配内存)、copyMemory(拷贝内存)、freeMemory(释放内存 )、getAddress(获取内存地址)、addressSize、pageSize、getInt(获取内存地址指向的整数)、getIntVolatile(获取内存地址指向的整数,并支持volatile语义)、putInt(将整数写入指定内存地址)、putIntVolatile(将整数写入指定内存地址,并支持volatile语义)、putOrderedInt(将整数写入指定内存地址、有序或者有延迟的方法)等方法。getXXX和putXXX包含了各种基本类型的操作。
    利用copyMemory方法,我们可以实现一个通用的对象拷贝方法,无需再对每一个对象都实现clone方法,当然这通用的方法只能做到对象浅拷贝。
  2. 不用new或反射也能去创建对象
    通常我们可以用new或者反射来实例化对象,使用allocateInstance()方法可以直接生成对象实例,且无需调用构造方法和其它初始化方法。
  3. 操作类、对象、变量
    这部分包括了staticFieldOffset(静态域偏移)、defineClass(定义类)、defineAnonymousClass(定义匿名类)、ensureClassInitialized(确保类初始化)、objectFieldOffset(对象域偏移)等方法。
    通过这些方法我们可以获取对象的指针,通过对指针进行偏移,我们不仅可以直接修改指针指向的数据(即使它们是私有的),甚至可以找到JVM已经认定为垃圾、可以进行回收的对象。
  4. 多线程同步(CAS和锁机制)
    锁机制:monitorEnter、tryMonitorEnter、monitorExit,但已被废弃。
    CAS:compareAndSwapInt、compareAndSwap等,是Unsafe类用的最多的方法,原子类的实现原理。
  5. 线程的挂起(park)和恢复(unpark)
    将一个线程进行挂起是通过park方法实现的,调用 park后,线程将一直阻塞直到超时或者中断等条件出现。unpark可以终止一个挂起的线程,使其恢复正常。整个并发框架中对线程的挂起操作被封装在 LockSupport类中,LockSupport类中有各种版本pack方法,但最终都调用了Unsafe.park()方法。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
jdk-11-windows是指Java开发工具包(Java Development Kit)的第11个版本,适用于Windows操作系统。JDK是使用Java语言进行开发的应用程序和软件的环境,它提供了许多工具和功能,可用于编写、编译和运行Java程序。 JDK-11包含了许多新的特性和改进,使得Java编程更加方便和高效。其中一项突出的特性是JVM(Java虚拟机)的性能改进,它可以提供更快速和更高效的执行速度。其他一些特性包括G1垃圾回收器的增强和适用于观察、诊断和监控Java程序的Java Flight Recorder和Java Mission Control工具等。 对于Windows用户来说,JDK-11提供了易于安装和使用的Windows平台上的开发环境。它包括了JRE(Java运行环境)和用于开发、调试和测试Java应用程序的工具集。通过JDK-11,开发人员可以利用Windows操作系统的优势和特性,进行快速、高效和可靠的Java开发。 此外,JDK-11还提供了许多与安全性和稳定性相关的改进。它包含了新的安全性特性,可以帮助开发人员保护他们的应用程序免受潜在的安全威胁。另外,JDK-11还包含了一些稳定性改进,可以提高应用程序的性能和可靠性。 总之,JDK-11-windows是适用于Windows操作系统的Java开发工具包的最新版本。它提供了许多新的特性和改进,使得Java开发更加方便、高效和安全。对于Windows用户来说,使用JDK-11可以提供更好的开发体验,使他们能够更轻松地创建出优秀的Java应用程序。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值