(1w字一篇理解透Unsafe类)Java魔法类:Unsafe详解

Java魔法类 Unsafe

  • 文章导读:(约12015字,阅读时间大约1小时)
  • 1. Unsafe介绍
  • 2. Unsafe创建
  • 3. Unsafe功能
    • 3.1内存操作
    • 3.2 内存屏障
    • 3.3 对象操作
    • 3.4 数组操作
    • 3.5 CAS操作
    • 3.6 线程调度
    • 3.7 Class操作
    • 3.8 系统信息
  • 4. 总结

JUC源码中的并发工具类出现过很多次 Unsafe类,它
的功能以及使用场景这篇进行介绍。

文章导读:(约12015字,阅读时间大约1小时)

1. Unsafe介绍

Unsafe是位于sun.misc包下的类,提供一些更底层的访问系统内存资源,和管理系统内存资源的方法,但是因为会访问系统的内存资源 变成和C语言一样的指针,指针的使用是有风险的,所以Unsafe也是有类似的风险,所以在使用的时候需要注意,过度或者不正确的使用可能导致程序出错。
但是,Unsafe类也使得Java增强了底层操作系统资源的能力。
同时,Unsafe提供的功能的实现是依赖于本地方法(Native Method)的,本地方法就是Java中使用其他语言写的方法,本地方法用native修饰,java只声明方法,具体实现由本地方法实现。

使用本地方法的原因

  • 需要使用到Java没有的特性,就得使用本地方法 来用别的语言来实现,比如Java没有什么底层操作系统的能力,所以要想在跨平台的同时还可以由底层控制的能力,就要使用本地方法。
  • 其他语言已经实现的功能,可以Java调用使用
  • Java在速度上比一些更底层的语言慢,如果程序对时间要求高或者对性能要求高,那么就需要使用更底层的语言。

JUC包的很多并发工具类在实现并发功能的时候,都调用了本地方法,用来提高Java的运行上限,同时为了能更底层的操作操作系统,也会使用本地方法,对于本地方法来说,不同操作系统的实现不太一样,但是使用起来是一样的。

2. 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方法获取实例。但是有个前提是 在调用getUnsafe方法的时候会对调用者的ClassLoader进行检查,也就是检查类加载器,如果是由Bootstrap classLoader加载的,那么才可以获得实例 如果不是那么就抛出异常SecurityException,所以,只有启动类加载器加载的类才可以调用Unsafe类中的方法,有助于避免被不可信代码调用,(因为这个原因说一在获取Unsafe类的实例的时候 大概率会抛出SecurityException异常,这样就要有别的方法来获取实例,比如下面讲到的反射方法)

那么为什么使用Unsafe类这么有限制?
Unsafe类提供的功能很底层,它可以访问系统资源,操作系统资源,所以存在一些安全风险,

在这个限制的条件下如何来获取Unsafe实例呢
我知道的方法是一个:
通过反射
我们可以通过反射来获得Unsafe类中以及完成实例化的theUnsafe对象

//通过反射获取Unsafe类中已经实例化完成的theUnsafe对象
    private static Unsafe reflectGetUnsafe() {
        try {
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            return (Unsafe) field.get(null);
        } catch (NoSuchFieldException e) {//处理异常
            e.printStackTrace();
            return null;//返回内容
        } catch (IllegalAccessException e) {
            e.printStackTrace();
            return null;
        }
    }

3. Unsafe功能

3.1内存操作

Java中不能直接操作内存,对象的分配内存和释放都是JVM完成的,在Unsafe中,提供了几个方法可以直接操作内存:

//分配新的本地空间
public native long allocateMemory(long bytes);
//重新调整内存空间的大小
public native long reallocateMemory(long address, long bytes);
//将内存设置为指定值
public native void setMemory(Object o, long offset, long bytes, byte value);
//内存拷贝
public native void copyMemory(Object srcBase, long srcOffset,Object destBase, long destOffset,long bytes);
//清除内存
public native void freeMemory(long address);

测试:

 public void  Test() {
        Unsafe unsafe = reflectGetUnsafe();
        int size = 4;
        long addr = unsafe.allocateMemory(size);
        long addr1 = unsafe.reallocateMemory(addr,size*2);
        System.out.println("addr:" + addr);
        System.out.println("addr1:" + addr1);
        try {
            unsafe.setMemory(null,addr,size,(byte) 1);
            for (int i = 0; i < 2; i++) {
                //o为哪个对象 addr为对象中的偏移量(offset) o1是另一个对象 l1为拷贝到哪里 l1为拷贝字节
                //进行了两次拷贝 每次拷贝将内存地址addr开始的四个字节拷贝到add1和add1+size*i(也就是4)的内存
                unsafe.copyMemory(null, addr, null, addr1 + size * i, 4);
            }
            System.out.println(unsafe.getInt(addr1));
            System.out.println(unsafe.getLong(addr1));
        } finally {
            //因为Unsafe的对象不会自动释放 所以要手动释放
            unsafe.freeMemory(addr);
            unsafe.freeMemory(addr1);
        }
    }
addr: 2433733895744
addr3: 2433733894944
16843009
72340172838076673

使用allocateMemory方法申请4字节长度的内存空间,调用setMemory方法在每个字节中写入byte类型的1,调用getInt方法的时候 取四个字节 就是00000001 00000001 00000001 00000001 十进制就是16843009

然后再代码中调用reallocateMemory 重新分配8字节的内存空间,在循环中 分别拷贝两次 每次拷贝内存地址addr的4个字节 拷贝到addr1和addr1+4的内存空间上。

此外由于这种分配是堆外内存,不能自动回收,所以要再finally中手动使用freeMemory释放。

使用堆外内存的好处

  • **对垃圾回收停顿改善,**因为推外内存不被JVM管理,所以在JVM垃圾回收的时候,可以减少垃圾回收停顿。
  • **提高I/O操作性能,**会存在堆内内存到堆外内存的拷贝,所以如果使用堆外内存的话,就可以提高IO操作性能,可以将频繁需要拷贝的内存 或者 生命周期短的内存直接放在堆外,避免在堆内的时候一直拷贝到堆外去。

应用场景:
DirectByBuffer是Java实现堆外内存的一个重要类,在通信过程中做缓冲池,在NIO框架中使用较多,DirectByBuffer的堆外内存创建,使用,释放,都是由Unsafe提供的堆外内存API来实现的

DirectByBuffer构造函数👇:

DirectByteBuffer(int cap) {                   // package-private

    super(-1, 0, cap, cap);
    boolean pa = VM.isDirectMemoryPageAligned();
    int ps = Bits.pageSize();
    long size = Math.max(1L, (long)cap + (pa ? ps : 0));
    Bits.reserveMemory(size, cap);

    long base = 0;
    try {
        // 分配内存并返回基地址
        base = unsafe.allocateMemory(size);
    } catch (OutOfMemoryError x) {
        Bits.unreserveMemory(size, cap);
        throw x;
    }
    // 内存初始化
    unsafe.setMemory(base, size, (byte) 0);
    if (pa && (base % ps != 0)) {
        // Round up to page boundary
        address = base + ps - (base & (ps - 1));
    } else {
        address = base;
    }
    // 跟踪 DirectByteBuffer 对象的垃圾回收,以实现堆外内存释放
    cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
    att = null;
}

创建DirectByBuffer的时候,通过unsafe.allocateMemory进行内存分配,然后使用unsafe.setMemory进行内存初始化,使用Cleaner类的create方法创建对象,这个对象可以用来跟踪DirectByBuffer对象,如果这个对象被垃圾回收了,那么分配的堆外内存也释放。

3.2 内存屏障

计算机的运行时,编译器和cpu会在保证结果不变的前提下,进行代码重排序提升性能,但是这样可能会导致CPU高速缓存的数据和内存中的数据不一致,所以内存屏障的作用减少避免这样的事情发生。
内存屏障的实现在不同操作系统上可能不一样,所以Java引入3个内存屏障,都是统一由JVM实现的,屏蔽了底层的差异
Unsafe中的桑个内存屏障:

//内存屏障,禁止load操作重排序。屏障前的load操作不能被重排序到屏障后,屏障后的load操作不能被重排序到屏障前
public native void loadFence();
//内存屏障,禁止store操作重排序。屏障前的store操作不能被重排序到屏障后,屏障后的store操作不能被重排序到屏障前
public native void storeFence();
//内存屏障,禁止load、store操作重排序
public native void fullFence();

对内存屏障的理解,使用内存屏障之前,如果有123操作,那么123是可以打乱来执行的,但是有了内存屏障之后,它可以规定某个点,它让在某一点之前的所有读写任务都执行完以后才可以进行点后任务。 比如123操作 可能会规定 执行完12操作才可以进行3操作,3操作不可以提前到1或2之前。

用fullFence来说 它就规定了在这个屏障之前的所以读写操作执行完以后,才能进行屏障之后的操作。

并且完成屏障前的操作后,会将缓存数据设置为无效,重新从主存中获取,这个功能的用处就是,可以让内存屏障在多线程下实现可见性(内存屏障本身只有禁止指令重排序,volatile可以精致指令重排序和内存可见性)
因为没有内存屏障的时候线程更新数据到主存上可能不及时,或者及时也没有被另一个线程发现,有了内存屏障 必须重新冲主存中获取数据,这样就保证了内存的可见性

代码举例:

@Getter
class ChangeThread implements Runnable{
    /**volatile**/ boolean flag=false;
    @Override
    public void run() {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("subThread change flag to:" + flag);
        flag = true;
    }
}

public static void main(String[] args){
    ChangeThread changeThread = new ChangeThread();
    new Thread(changeThread).start();
    while (true) {
        boolean flag = changeThread.isFlag();
        unsafe.loadFence(); //加入读内存屏障
        if (flag){
            System.out.println("detected flag changed");
            break;
        }
    }
    System.out.println("main thread end");
}

subThread change flag to:false
detected flag changed
main thread end

如果没有loadFence方法 那么主线程没有办法感知到flag的变化,因为在Java内存模型中,运行的线程不是直接读取主存中的变量,而是修改线程本身的工作内存的变量,然后传给主内存的,并且线程和线程之间的内存是独立的,如果主线程要感知flag变化,那么就应该借助于主内存,主内存将修改后的flag同步给主线程,因为loadFence屏障之前的线程执行结束了,将缓存数据设置为无效,然后重新读取最新的主存内容,只有在合适的地方加入屏障,才可以保证内存可见性。

!!!保证内存可见性是因为它会立刻刷新屏障前的数据到主存中去,不保证内存可见性是因为一个线程写入的数据 不一定会被另一个线程立刻可见,而屏障就是保证了让他立刻可见后,才让另一个线程拿到正确的数据。

应用场景:
Java8中的一种锁机制——StampedLock,读写锁的一个改进版本,是一种乐观读锁不会阻塞写线程获取写锁,缓解读多写少的时候,有的线程用的少的情况,但是因为不阻塞写锁,所以线程共享变量从主存到线程内存的时候,会有数据的不一样
解决方法,stampedLock的validate方法会用过Unsafe1的loadFence方法加入一个load内存屏障,来防止指令重排序(禁止读操作重排序,内存屏障前的读操作不能重排序到内存屏障之后)

public boolean validate(long stamp) {
   U.loadFence();
   return (stamp & SBITS) == (state & SBITS);
}

3.3 对象操作

Unsafe提供了全部的8种基础数据类型以及Object的put和get方法,并且所由的put方法都可以直接修改内存种的数据。

Unsafe提供volatile读写和有序写入方法。

//在对象的指定偏移地址处读取一个int值,支持volatile load语义
public native int getIntVolatile(Object o, long offset);
//在对象指定偏移地址处写入一个int,支持volatile store语义
public native void putIntVolatile(Object o, long offset, int x);

有序写入的成本比volatile低,因为只保证有序写入不保证可见性,也就是一个线程写入的值不能保证其他线程立刻可见,这是有关内存屏障的。

  • Load:读,将主存种的数据拷贝到处理器的缓存中
  • Store:写,将处理器中的缓存拷贝到主存中

!!内存屏障可以实现内存可见性和顺序写入的原因:
顺序写入和volatile写入的区别在于:

  • 顺序写入是在写时加入内存屏障的类型为StoreStore类
  • volatile写入时加入的内存屏障类型时StoreLoad类型
    在这里插入图片描述

在顺序写入的时候使用StoreStore类型,可以保证Store1立刻刷新数据到主存中去,然后才可以进行Store2的后续操作。
在volatile写入的时候使用StoreLoad类型,保证Store1立刻刷新数据到主存中去,然后才可以进行Load2及后续操作,并且StoreLoad屏障会让在屏障之前的所有指令全部完成以后,才执行屏障之后的指令。
三个写入方法的效率put>putOrder>putVolatile

对象实例化:
使用Unsafe的allocateInstance方法,进行非常规的对象实例化。

@Data
public class A {
    private int b;
    public A(){
        this.b =1;
    }
}

public void objTest() throws Exception{
    A a1=new A();
    System.out.println(a1.getB());
    A a2 = A.class.newInstance();
    System.out.println(a2.getB());
    A a3= (A) unsafe.allocateInstance(A.class);
    System.out.println(a3.getB());
}

应用场景:

  • 常规对象实例化方法:使用new来创建对象,但是使用new的话,如果类只有一个有参构造方法也没有生命无参构造方法的时候,必须使用有参构造方法来构造对象。
  • 非常规对象实例化方法:Unsafe中的allocateInstance方法,通过class对象就可以构造实例,不用调用构造方法,JVM安全检查,构造器是private修饰的 也可以实例化,这个allocateInstance在Gson(反序列化)中也有用到过。

3.4 数组操作

arrayBaseOffestarrayIndexScale这两个方法配合使用,定位数字中每个元素在内存中的位置。

//返回数组中第一个元素的偏移地址
public native int arrayBaseOffset(Class<?> arrayClass);
//返回数组中一个元素占用的大小
public native int arrayIndexScale(Class<?> arrayClass);

3.5 CAS操作

CAS:意思是比较和替换的意思,在并发编程中经常会用到CAS来保证数据的正确性,是一个原子操作,CAS中有三个参数(内存位置的值,预期值,新值),执行时会将内存位置的值和预期值比较,如果相同就会吧内存位置的值替换为新值,如果不同就不会替换。

Unsafe提供的CAS方法底层实现是CPU指令cmpxchg

在Unsafe类中提供了三种CAS类型:
compareAndSwapObject、compareAndSwapInt、compareAndSwapLong

/**
	*  CAS
  * @param o         包含要修改field的对象
  * @param offset    对象中某field的偏移量
  * @param expected  期望值
  * @param update    更新值
  * @return          true | false
  */
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);

应用场景:
在JUC包的并发工具类中使用了很多CAS操作,如synchronizedAQS都有使用。‘

比如compareAndSwapInt:

public final native boolean compareAndSwapInt(Object o, long offset,int expected,int x);

其中o为要操作的对象,offset是偏移量,expected是预期值,x是替换的值,如果offset的值和expected的值一样,就会把offset这个字段的值更新为x。

使用1加到10的多线程环境下的例子:

import sun.misc.Unsafe;

public class CasTest {
//    public static void main(String[] args) throws NoSuchFieldException {
//        Field field = Unsafe.class.getField("theUnsafe");
//        Demo1 demo1 = new Demo1(field);
//    }
    private void increment(int x) {
        Unsafe unsafe = Demo.reflectGetUnsafe();
        while (true) {
            try {
                //拿到全局变量的a值 初始为0
                long offset = unsafe.objectFieldOffset(CasTest.class.getDeclaredField("a"));
                //第一次 当offset(a=0)等于x-1(x=1)的时候 把x替换到offset上
                if (unsafe.compareAndSwapInt(this,offset,x-1,x)){
                    break;
                }
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            }
        }
    }

    private volatile int a;

    public static void main(String[] args) {
        CasTest casTest = new CasTest();
        new Thread(() -> {
            for (int i = 1; i < 5; i++) {
                casTest.increment(i);
                System.out.print(casTest.a+" ");
            }
        }).start();

        new Thread(()->{
            for (int i = 5; i < 10; i++) {
                casTest.increment(i);
                System.out.print(casTest.a+" ");
            }
        }).start();
    }
}

解释:
因为多线程环境下 第一个线程加到2的时候,第二个线程从5开始加,他进入到increment进行CAS比较的时候会发现内存中的a值(a=2)并不是期望值(x-1=4),那么就不会执行CAS操作把替换值(x=5)给内存中的a值,就会一直循环下去知道a被加到了正确的值的时候,这时候if才会成立然后才break结束这一次任务。

3.6 线程调度

Unsafe类中提供了parkunpark,monitorEnter,monitorExit,tryMonitorEnter方法进行线程调度

//取消阻塞线程
public native void unpark(Object thread);
//阻塞线程
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取消阻塞,monitorEnter获取对象锁,monitorExit释放对象锁,tryMonitorEnter尝试获得对象锁。

在Unsafe源码中 除了park和unpark 剩下的方法以及不建议使用了。

应用场景:
抽象队列同步器 AbstractQueuedSynchronizer(AQS)就是通过调用 LockSupport.park()LockSupport.unpark() 实现线程阻塞和线程唤醒的,而其实他的park和unpark调用的是Unsafe类的park和unpark方法。

public static void park(Object blocker) {
    Thread t = Thread.currentThread();
    setBlocker(t, blocker);
    UNSAFE.park(false, 0L);
    setBlocker(t, null);
}
public static void unpark(Thread thread) {
    if (thread != null)
        UNSAFE.unpark(thread);
}

对Unsafe的park和unpark方法测试👇

public static void main(String[] args) {
    Thread mainThread = Thread.currentThread();
    Unsafe unsafe = Demo.reflectGetUnsafe();
    new Thread(()->{
        try {
            TimeUnit.SECONDS.sleep(5);
            System.out.println("取消阻塞主线程");
            unsafe.unpark(mainThread);//取消主线程阻塞
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }).start();

    System.out.println("阻塞主线程");
    unsafe.park(false,0l);
    System.out.println("取消成功");
}
阻塞主线程
取消阻塞主线程
取消成功

子线程运行时睡眠,主线程打印内容并且阻塞自己,子线程睡眠结数回复工作 打印内容以后唤醒主线程,主线程打印最后的内容。

3.7 Class操作

Unsafe对Class的相关操作主要包括类加载静态变量的操作方法

//获取静态属性的偏移量
public native long staticFieldOffset(Field f);
//获取静态属性的对象指针
public native Object staticFieldBase(Field f);
//判断类是否需要初始化(用于获取类的静态属性前进行检测)
public native boolean shouldBeInitialized(Class<?> c);

应用场景:
Lambda表达式需要实现依赖Unsafe的defineAnonymousClass方法定义实现相应的函数式接口的匿名类。

3.8 系统信息

两个获取系统信息的方法

//返回系统指针的大小。返回值为4(32位系统)或 8(64位系统)。
public native int addressSize();
//内存页的大小,此值为2的幂次方。
public native int pageSize();

4. 总结

介绍了Unsafe的概念功能有哪些,和使用的方法场景,Unsafe可以让我们更底层的操作 操作系统,便捷的访问操作系统内存,管理操作系统内存资源,但是带来的安全隐患也是需要注意的,堆外内存的处理,指针安全的处理等等。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/215721.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

外包干了4年,技术退步太明显了。。。。。

先说一下自己的情况&#xff0c;本科生生&#xff0c;18年通过校招进入武汉某软件公司&#xff0c;干了接近4年的功能测试&#xff0c;今年国庆&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了四年的功能测…

netcore swagger 错误 Failed to load API definition

后端接口报错如下&#xff1a; 前端nswag报错如下&#xff1a; 根据网上查询到的资料说明&#xff0c;说一般swagger这种错误都是控制器里有接口代码异常造成的&#xff0c;通常是接口没有加属性Attribute&#xff0c; 比如[HttpPost("Delete")]、[HttpGet("Del…

基于ssm的疫苗预约系统(有报告)。Javaee项目。ssm项目。

演示视频&#xff1a; 基于ssm的疫苗预约系统&#xff08;有报告&#xff09;。Javaee项目。ssm项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&#xff0c;通过Spring Spri…

从这7点重构品牌企业的业务中台系统|徐礼昭

文&#xff5c;徐礼昭 &#xff08;商派市场负责人&#xff0c;RRL重构零售实验室负责人&#xff09; 重构或者升级企业的数字化系统究竟有多重要&#xff1f; 笔者列举两个简单的数字化项目案例&#xff0c;数据仅供大家参考—— &#xff08;1&#xff09;某连锁企业线上云店…

【OpenCV】计算机视觉图像处理基础知识

目录 前言 推荐 1、OpenCV礼帽操作和黑帽操作 2、Sobel算子理论基础及实际操作 3、Scharr算子简介及相关操作 4、Sobel算子和Scharr算子的比较 5、laplacian算子简介及相关操作 6、Canny边缘检测的原理 6.1 去噪 6.2 梯度运算 6.3 非极大值抑制 6.4 滞后阈值 7、Ca…

mockito加junit实现单元测试笔记

目录 一、简介1.1 单元测试的特点1.2 mock类框架使用场景1.3 常用mock类框架1.3.1 mockito1.3.2 easymock1.3.3 powermock1.3.4 JMockit 二、mockito的单独使用2.1 mock对象与spy对象2.2 初始化mock/spy对象的方式初始化mock/spy对象第1种方式初始化mock/spy对象第2种方式初始化…

数据“表”的增删改查

创建数据表 删除数据表 修改数据表 查看数据表 喜欢点赞收藏&#xff0c;如有疑问&#xff0c;点击链接加入群聊【信创技术交流群】&#xff1a;http://qm.qq.com/cgi-bin/qm/qr?_wv1027&kEjDhISXNgJlMMemn85viUFgIqzkDY3OC&authKey2SKLwlmvTpbqlaQtJ%2FtFXJgHVgl…

全球与中国HDPE管道市场:增长趋势、竞争格局与前景展望

快速成长的人口、快速的经济成长和工业发展增加了对可靠供水系统的需求。工业需要为制造流程、冷却系统和卫生目的提供可靠的水供应。随着国家的发展&#xff0c;它们更加重视基础设施&#xff0c;包括供水系统&#xff0c;以支持工业成长。HDPE管道广泛应用于饮用水和灌溉的配…

LeetCode 1038. 从二叉搜索树到更大和树:(反)中序遍历

【LetMeFly】1038.从二叉搜索树到更大和树&#xff1a;&#xff08;反&#xff09;中序遍历 力扣题目链接&#xff1a;https://leetcode.cn/problems/binary-search-tree-to-greater-sum-tree/ 给定一个二叉搜索树 root (BST)&#xff0c;请将它的每个节点的值替换成树中大于…

虹科技术 | BabyLIN产品如何轻松搞定K线协议实现?

概述&#xff1a;为了实现K线通信&#xff0c;SDF-V3在协议部分中定义了新的协议类型KLine Raw。所有能够运行SDF-V3文件&#xff08;LinWorks版本在V.2.29.4以上&#xff09;并使用最新的固件&#xff08;固件版本在V.6.18以上&#xff09;的BabyLIN设备都可以执行KLine Raw协…

【23-24 秋学期】NNDL 作业12 优化算法2D可视化

简要介绍图中的优化算法&#xff0c;编程实现并2D可视化 1. 被优化函数 2. 被优化函数 3. 分析各个算法的优缺点 REF&#xff1a;图灵社区-图书 (ituring.com.cn) 深度学习入门&#xff1a;基于Python的理论与实现 NNDL 作业11&#xff1a;优化算法比较_"ptimizers[…

MYSQL报错 [ERROR] InnoDB: Unable to create temporary file; errno: 0

起因 服务器的mysql不支持远程访问&#xff0c;在修改完相关配置后重启服务出错。 2023-12-03T10:12:23.895459Z 0 [Note] C:\Program Files\MySQL\MySQL Server 5.7\bin\mysqld.exe (mysqld 5.7.22-log) starting as process 15684 ... 2023-12-03T10:12:23.908886Z 0 [Note…

YOLOv8独家原创改进:创新自研CPMS注意力,多尺度通道注意力具+多尺度深度可分离卷积空间注意力,全面升级CBAM

💡💡💡本文自研创新改进:自研CPMS, 多尺度通道注意力具+多尺度深度可分离卷积空间注意力,全面升级CBAM 1)作为注意力CPMS使用; 推荐指数:五星 CPMS | 亲测在多个数据集能够实现涨点,对标CBAM。 收录 YOLOv8原创自研 https://blog.csdn.net/m0_63774211/ca…

内存是如何工作的

一、什么是内存 从外观上辨识&#xff0c;它就是内存条&#xff1b;从硬件上讲&#xff0c;它叫RAM&#xff0c;翻译过来叫随机存储器。英文全称&#xff1a;Random Access Memory。它也叫主存&#xff0c;是与CPU直接交换数据的内部存储器。其特点是读写速度快&#xff0c;不…

一文搞懂系列——动态库的加载方式及应用场景

引文 我们在工作中经常会遇到动态库链接的问题&#xff0c;因为正常的方式并不能满足我们的场景。常见的问题可以总结如下&#xff1a; 系统路径默认路径、usr/lib、/lib 目录&#xff0c;不会集成第三方动态库。 同名动态库可能在多个路径中存在。 针对不同的场景&#xff0…

替代AMS1117-ADJ可调输出线性稳压器(LDO)

1、概 述 PC1117-ADJ/1.2/1.5/1.8/2.5/2.85/3.3/5是最大输出电流为1A的低压降正向稳压器&#xff0c;其中 PC1117-ADJ是可调输出电压版&#xff0c;只需要两个外接电阻即可实现输出电压在1.25V~13.8V范围内的调节&#xff0c;而PC1117-1.2/1.5/1.8/2.5/2.85/3.3/5是固定输出1.…

【陈老板赠书活动 - 19期】-2023年以就业为目的学习Java还有必要吗?

陈老老老板&#x1f9b8; &#x1f468;‍&#x1f4bb;本文专栏&#xff1a;赠书活动专栏&#xff08;为大家争取的福利&#xff0c;免费送书&#xff09; &#x1f468;‍&#x1f4bb;本文简述&#xff1a;生活就像海洋,只有意志坚强的人,才能到达彼岸。 &#x1f468;‍&am…

vector向量详解,小白快速入门

1.vector是什么 vector名为向量&#xff0c;其实就是一个长度可变的数组 是连续的顺序的储存结构&#xff08;和数组一样的类别&#xff09;&#xff0c;但是有长度可变的特性。 2.vector的初始化 vector<int> v; 一维可变数组&#xff0c;类型为int&#xff0c;名称…

xampp环境安装

XAMPP是完全免费且易于安装的Apache发行版&#xff0c;其中包含Apache、MariaDB、PHP和Perl。 类似XAMPP的服务器套件还有很多&#xff0c;我用过的还有UPUPW&#xff0c;它们都极大的简化了开发环境的配置。 下载链接Download XAMPP 我选的最新的 一路next就安装好了。

Cesium 太阳光晕

Cesium 太阳光晕 基于后处理实现位置动态跟随太阳实际位置可以动态改变颜色 viewer.camera.flyTo({destination: { "x": -2471386.549378386, "y": 4838798.836366257, "z": 3329936.5717575867 },duration: 0,orientation: {heading: Cesium.M…