【Java 并发编程】多线程安全问题(上)

news2024/12/27 9:54:11

前言


        虽然并发编程让我们的 CPU 核心能够得到充分的使用,程序运行效率更高效。但是也会引发一些问题。比如当进程中有多个并发线程进入一个重要数据的代码块时,在修改数据的过程中,很有可能引发线程安全问题,从而造成数据异常

public class Demo3 {
    static int Count = 0;
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            for(int i = 0;i<50000;i++){
                Count++;
            }
        });
        Thread t2 = new Thread(()->{
            for(int i = 0;i<50000;i++){
                Count++;
            }
        });
        t1.start();
        t2.start();

        t1.join();
        t2.join();
        System.out.println(Count);
    }
}

        以上输出结果是一个小于 100000 的随机值,输出结果不符合预期说明代码有 bug ~

        补充解释:此处代码中的 count++ 操作,其实在 CPU 视角来看,是 3 个指令

(1)把内存中的数据,读取到 cpu 寄存器里
(2)把 CPU 寄存器里的数据 +1
(3)把寄存器的值,写回内存

        然而,我们的程序是并发进行的,可能正在执行 线程t1 的某些指令,就立刻切换到执行 线程t2 的某些指令。如下图所示:当 t1 执行到第二个指令时,由于程序是并发执行的,在这个时候立刻切换到 t2线程 的指令1。注意,此时 t1线程虽然寄存器存着修改后的值,但是内存并没有修改,所以我们 t2线程 去获取内存中的值仍然是0。 至 t2线程 完成三项指令后,此时 Count内存 被修改成了1,现在执行 t1线程 的指令3(修改内存数据),但是我们 t1寄存器 存的值没有更新,仍然是 1。所在这里相当于我们 for 循环执行了两次,Count 却只增加了 1。这就是简单的线程安全问题。

        如何解决以上问题?其实有两种办法,(1)设置两个变量分别记录两个线程的值,最后在整合。(2)使用 synchronized 同步锁。

        想要了解更多关于线程安全的问题,请继续阅读本篇文章 ~


前期回顾:认识多线程


目录

前言

预备知识

Thread 类的属性与方法

Thread 的构造⽅法

 Thread 的常⻅属性

后台线程与前台线程

理解前台线程与后台线程

线程的生命周期

线程的操作

启动一个线程                                                                                                                   

中断⼀个线程

等待⼀个线程

获取当前线程引⽤

预备知识


Thread 类的属性与方法

介绍

        Thread 类是 JVM ⽤来管理线程的⼀个类,换句话说,每个线程都有⼀个唯⼀的 Thread 对象与之关联。

Thread 的构造⽅法

方法说明
Thread()创建线程对象
Thread(Runnable target)使用 Runnable 对象创建线程对象
Thread(String name)创建线程对象,并命名
Thread(ThreadGroup group,Runnable target)使用 Runnable 对象创建线程对象,并命名

        以上一二种方法在上期内容中有介绍,现在详细介绍由我们自己命名的线程该如何观察调试:

    public class Dome4 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(()->{
            while(true){
                System.out.println("Hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"自定义线程");
        thread.start();

        while(true){
            System.out.println("Hello main");
            Thread.sleep(1000);
        }

    }
}

        我们可以使用 JDK文件夹 中的 jconsole 命令观察线程,通过上图所示自命名的线程就已经创建出来了。这种方法可以用来调试线程。

        ThreadGroup 是线程组,把多个线程放在一组里,方便统一的设置线程的一些属性,但是现在很少使用线程组。线程的相关属性也用的不是太多,应该就是调试线程的时候需要用到。现在更多的是会使用线程池。

 Thread 的常⻅属性

• ID是线程的唯⼀标识,不同线程不会重复(JVM自动分配,无需手动创建)
• 名称是各种调试⼯具⽤到(自命名的名称,未自命名的进程默认以 Thread-0、Thread-1等进行)
• 状态表⽰线程当前所处的⼀个情况,下⾯我们会进⼀步说明
• 优先级⾼的线程理论上来说更容易被调度到
• 关于后台线程,需要记住⼀点:JVM会在⼀个进程的所有⾮后台线程结束后,才会结束运⾏
• 是否存活,即简单的理解,为 run() ⽅法是否运⾏结束了

    public static void main(String[] args) {
        Thread t1 = new Thread(()->{
            while(true){
                System.out.println("Hello Thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t1.start();
        // 获取线程属性
        System.out.println("线程ID:"+t1.getId());
        System.out.println("线程名称:"+t1.getName());
        System.out.println("线程状态:"+t1.getState());
        System.out.println("线程优先级:"+t1.getPriority());
    }

输出结果:

Hello Thread
线程ID:30
线程名称:Thread-0
线程状态:TIMED_WAITING
线程优先级:5
Hello Thread
Hello Thread
...

后台线程与前台线程

后台线

        所谓的后台线程也叫守护线程,是指在程序运行的时候在后台提供的一种通用的服务线程,并且这种线程并不属于程序不可或缺的部分。因此,当所有的前台进程结束时,程序也就终止了,同时会杀死进程中的所有后台进程。

前台线

        所谓的前台线程,是指在执行过程中能够影响进程结束的就是前台线程。比如,执行的我们 main 方法就是一个前台线程。 

public class Demo6 {
    public static void main(String[] args) {
        Thread t1 = new Thread(()->{
            while(true){
                System.out.println("Hello Thread~");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t1.start();
    }
}
Hello Thread~
Hello Thread~
Hello Thread~
Hello Thread~
Hello Thread~
...

        以上是我们最常见的代码,其实我们的 t1 是一个前台线程,因为它是一个死循环阻止了程序的结束。那么如何让它变成后台线程呢?Java 提供了 setDaemon() 方法,只需传个 true 即可将前台线程变成后台线程。

        此时,t1线程 被设置为后台线程,一旦 前台线程main 完成工作,就没有什么能够阻止程序终止了。因为此时除了后台线程之外,已经没有前台进程在运行了。

        我们可以对 main进程 进行短暂休眠 sleep() ,就可以观察所有后台进程启动后的结果:

理解前台线程与后台线程

        我们举个生活中的例子 -- 酒桌文化

        在我们的酒桌文化上:如果是程序员(下属)喝趴下了,可以选着溜走,但是酒局继续。我们把程序员看作是后台线程,后台线程不影响程序的运行。以例子来讲 “这个酒局就算不没有你,也能继续喝”。        但是如果是领导喝趴下了,说要离开,那么宴席就是真正的结束了。就算程序员跳出来说,还想再吃点,也不行。我把领导看作是前台线程,前台线程影响程序的运行。以例子来讲 “这个酒局就是因为领导而设置的,领导说喝到什么时候就什么时候”。

线程的生命周期

        在代码中,创建的 new Thread 对象的生命周期与内核中的实际的线程的生命周期是不一定一样的。可能会出现 new Thread 对象仍然存在,但是内核线程不存在了这种情况。这种情况主要是两种因数导致的:

调用 start  之前,因为此时内核中还没有创建线程,但是 Thread 对象已经创建了
线程 run 运行完毕后,内核的线程已经没有了,但是 Thread 对象仍然还在

        这个时候我们就可以使用 isAlive 进行区分,如果返回值为 true 则表明内核的线程存在,反之则不存在:

public class Demo6 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            for (int i = 0; i < 3; i++) {
                System.out.println("Hello Thread~");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        System.out.println("线程是否存活"+t1.isAlive());
        t1.start();
        System.out.println("线程是否存活"+t1.isAlive());
        Thread.sleep(4000);
        System.out.println("线程是否存活"+t1.isAlive());
    }
}

打印结果:

线程是否存活false
Hello Thread~
线程是否存活true
Hello Thread~
Hello Thread~
线程是否存活false

线程的操作


启动一个线程                                                                                                                   

        之前我们已经看到了如何通过覆写 run ⽅法创建⼀个线程对象,但线程对象被创建出来并不意味着线程就开始运⾏了,而是要调用 start 才能真正的运行线程。

模拟面试:start 与 run 之间的差别是什么呢?为什么 start 只能调用一次呢?

start 与 run 之间的差别  

start()方法是Java线程约定的内置方法,能够确保代码在新的线程上下文中运行
start()方法能够调用系统方法,真正在系统内核中创建线程(创建PCB,加入到链表中)。run()方法是我们自己写的代码,很显然没有这个能力
如果直接调用run()方法,那么它只是一个普通的方法调用,程序中依然只有一个主线程,并且只能顺序执行,需要等待run()方法执行结束后才能继续执行后面的代码

我们创建线程的目的是为了更充分地利用CPU资源,如果直接调用run()方法,就失去了创建线程的意义

start 不能运行两次的原因:

public class Demo7 {
    public static void main(String[] args) {
        Thread t1 = new Thread(()->{});
        t1.start();
        t1.start();
    }
}

         通过以上代码的执行结果,我们可以看到当 start 运行两次后会抛出运行时异常。这里就涉及到 “线程状态问题”,由于 Java 中希望一个 Thread 对象,只能对应一个系统中的线程。因此就会在 start 中,根据线程状态做出判定。

        如果 Thread 对象没有 start ,此时的状态就是一个 NEW(新建)状态,接下来就可以顺利的调用 satrt ,如果已经调用过 satrt 就会进入其他状态。只要不是 NEW 状态,接下来执行 start 都会抛出异常。关于线程状态待会会讲。

中断⼀个线程

        当一个线程的 run 方法返回时(执行了方法体中的最后一条语句后,执行 return 语句返回),或者出现方法未捕获的异常,这个线程将终止。假设某个场景一个线程A运行中需要另一个线程B结束,Java 中并不是直接终止线程B,而是通过一些办法让线程B的 run 方法执行完毕。

public class Demo8 {
    private static boolean isQuit = false;

    public static void main(String[] args) throws InterruptedException {
        Thread t1 =new Thread(()->{
            while(!isQuit){
                System.out.println("Hello World~");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("t1 线程执行结束");
        });
        t1.start();
        Thread.sleep(2000);
        System.out.println("main 线程尝试终止 t1 线程");
        isQuit = true;
    }
}

程序打印的结果 

Hello World~
Hello World~
main 线程尝试终止 t1 线程
t1 线程执行结束

        我们可以看到看我们的 main 终止 t1线程时,程序就已经结束了。这是因为 t1 是前台线程,但我们终止 t1 线程时,程序中唯一的前台线程 main 已经执行完毕,所以程序结束。

        Java 中提供了一个 interrupt 方法来请求终止一个线程当一个线程调用 interrupt 方法时,就会设置线程的中断状态。这是每个线程都有的一个boolean标志。各线程都应该不时的检查这个标志,以判断线程是否中断。

        为了确定是否设置了中断状态,首先调用静态方法 Thread.currentThread(获取本对象的实例),来获取当前线程,然后再调用 isInterrupted 方法。

        将上述代码改造一下就变成这样:

public class Demo8 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 =new Thread(()->{
            Thread currentThread = Thread.currentThread();
            while(!currentThread.isInterrupted()){
                System.out.println("Hello World~");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("t1 线程执行结束");
        });
        t1.start();
        Thread.sleep(3000);
        System.out.println("main 线程尝试终止 t1 线程");
        t1.interrupt();
    }
}

方法说明
public void interrupt()中断对象关联的线程
public static boolean interrupted()判断当前线程的中断标志位是否设置
public boolean isinterrupted()判断对象关联的线程的标志位是否设置

        但是此时代码就会抛出一个异常,这是为什么呢? 

        由于判定 isInterrupted 和执行打印这两个操作实在是太快了,因此整个循环花费的时间主要是在 sleep 的 1000 毫秒上,当我们调用 Interrupt 的时候,大概率 t1线程还处于 sleep 状态。此处 Interrupt 不仅仅能设置标志位,还能把 sleep 操作 “唤醒”。

        如果线程被阻塞,就无法检查中断状态。在一个被 sleep 或者 wait 调用阻塞的线程上调用 interrupt 方法时,那个阻塞调用将会被一个 InterruptedException 异常中断。

        InterruptedException 异常主要是因为 catch 中默认代码再次抛出的异常引起的,再次抛出的异常没人处理 ,就最终到了JVM这一层,进程就直接异常终止了。

        此时 sleep 被唤醒了,触发了异常被 catch 住了。虽然我们将 catch 的抛出异常屏蔽之后,程序没有抛出异常,但是线程t1没有被终止,这是什么情况呢?我们接着来看

        如果在每次工作迭代之后都需要调用 sleep 方法(或者其他中断方法),isInterrupted 检查是既没有必要也没有用处的。如果设置了中断状态,此时倘若用了 sleep 方法,它不会休眠,而是清除中断状态,并抛出 InterruptedException。这就是上述程序没有被中断的原因。

         解决以上问题共有三种 方法

《1》我们可以在 catch 中调用 Thread.currentThread().interrupt() 来设置中断状态。这样一来调用者就可以检测中断状态。

                catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    System.out.println("执行到 catch 操作");
                }

运行结果:

Hello World~
Hello World~
Hello World~
main 线程尝试终止 t1 线程
执行到 catch 操作
t1 线程执行结束

《2》我们可以在 catch 中使用 renturn 来终止循环。

                catch (InterruptedException e) {
                    System.out.println("执行到 catch 操作");
                    return;
                }

《3》更好的选着是:用 thow InterruptedException 标记你的方法,并去掉 try 语句块。这样调用者就可以捕获这个异常了。

等待⼀个线程

        操作系统中,针对多线程的执行,是一个 “随机调度,抢占执行” 的过程。线程等待机就是在确认两个线程的 “结束顺序”。因为多线程是并发执行的,无法确定两个现场的执行顺序,但是可以控制,谁先结束,谁后结束。让后结束的线程等待先结束的线程即可。因此我们可以让后结束的线程进入阻塞,让先结束的线程真的结束了,才结束阻塞

public class Demo10 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(()->{
            for (int i = 0; i < 3; i++) {
                System.out.println("这是线程 t");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("线程t 结束");
        });
        t.start();
        System.out.println("main 线程开始等待");
        t.join();
        System.out.println("main 线程等待结束");
    }
}

打印结果: 

main 线程开始等待
这是线程 t
这是线程 t
这是线程 t
线程t 结束
main 线程等待结束

        因为我们使用了 t.join 说明其他线程要等待 t线程 运行结束,此时 main线程 阻塞等待,当 t线程 执行完毕,join才会返回,我们的 main线程 才开始继续执行。

        问:如果 t线程 已经结束了还会对 main线程造成阻塞吗?

  public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(()->{
            for (int i = 0; i < 3; i++) {
                System.out.println("这是线程 t");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("线程t 结束");
        });
        t.start();
        Thread.sleep(4000);
        System.out.println("main 线程开始等待");
        t.join();
        System.out.println("main 线程等待结束");
    }
}

打印结果:

这是线程 t
这是线程 t
这是线程 t
线程t 结束
main 线程开始等待
main 线程等待结束

        根据上述例子:我们可以发现当 t1线程 结束后,main 线程立刻就结束了,说明当一个线程结束后,使用 join 无法对其他线程造成影响。

一个线程等待多个线程

public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            for (int i = 0; i < 3; i++) {
                System.out.println("这是线程 t1");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("线程t1 结束");
        });

        Thread t2 = new Thread(()->{
            for (int i = 0; i < 5; i++) {
                System.out.println("这是线程 t2");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("线程t2 结束");
        });

        t1.start();
        t2.start();
        System.out.println("main 线程开始等待");
        t1.join();
        t2.join();
        System.out.println("main 线程等待结束");
    }
}

运行结果:

main 线程开始等待
这是线程 t1
这是线程 t2
这是线程 t1
这是线程 t2
这是线程 t1
这是线程 t2
这是线程 t2
线程t1 结束
这是线程 t2
线程t2 结束
main 线程等待结束

        上述代码逻辑是:main线程等待 t1、t2线程,由于程序是并发进行的,所以前 3s t1、t2 是一起计时的,当 t1线程 执行完毕,t2 线程只需在执行剩余的时间即可。当所有被等待线程执行完后,main 线程就可以开始运行了。

        那么如果是 t2线程 等待 t1线程 该如何写呢? -- 注意,被等待线程想要阻塞谁,就在阻塞线程中调用自己的 join 即可。

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            for (int i = 0; i < 3; i++) {
                System.out.println("这是线程 t1");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("线程t1 结束");
        });

        Thread t2 = new Thread(()->{

            try {
                t1.join();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

            for (int i = 0; i < 5; i++) {
                System.out.println("这是线程 t2");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("线程t2 结束");
        });

        t1.start();
        t2.start();
        System.out.println("main 线程开始等待");
        t1.join();
        t2.join();
        System.out.println("main 线程等待结束");
    }
}

运行结果:

main 线程开始等待
这是线程 t1
这是线程 t1
这是线程 t1
线程t1 结束
这是线程 t2
这是线程 t2
这是线程 t2
这是线程 t2
这是线程 t2
线程t2 结束
main 线程等待结束
方法说明
public void join()等待进程结束
public void join(long millis)等待线程结束,最多等 millis 毫秒
public void join(long millis, int nanos)同理,可以更高精度

        我们以上的 join 都是无参版本,这意味着一定要等待这个线程执行完毕才能取消阻塞,那么如果被等待线程出现了一些问题,不能正常的结束,那么一直阻塞无法执行其他操作了。

        join 还提供了带参数的版本,说明到达一定时间被等待线程没有结束,那么阻塞线程就不等了直接开始运行。

获取当前线程引⽤

方法说明
Thread.currentThread()返回当前线程对象的引用
public class Demo11 {
    public static void main(String[] args) {
        Thread mainThread = Thread.currentThread();
        System.out.println(mainThread.getName()+"线程");
        Thread t1 = new Thread(()->{
            Thread t1Thread = Thread.currentThread();
            System.out.println(t1Thread.getName()+"线程");
        },"t1线程");
        t1.start();
    }
}

运行结果: 

main线程
t1线程线程

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

可视化-最小二乘法拟合直线

目录 1、最小二乘法拟合直线 2、需要用到的公式 3、计算各个参数 &#xff0c;得到函数表达式 4、可视化-绘画图像 1、最小二乘法拟合直线 double x[15] {29,34,39,44,49,54,59,64,69,74,79,84,89,94,99}; double y[15] { 0.2989,0.3036,0.3084,0.3133,0.3182,0.3231,0.…

【HTML并不简单】笔记3-你不知道的列表元素和html、body

文章目录 无序列表menuol的其他属性typestart和valuereversed 定义列表dl、dt、ddhtml与body关联性overflow 桌面端和移动端的滚动条 《HTML并不简单&#xff1a;Web前端开发精进秘籍》张鑫旭 笔记 无序列表menu <menu>元素可以看成是<ul>元素的平行替代&#xff…

生成正激波表的代码

k1.4 import math import numpy as np import pandas as pd #Ma1到p之比 def Ma2p(Ma1,k):return 2*k*Ma1**2/(k1)-(k-1)/(k1) def Ma2rho(Ma1,k):return (k1)*Ma1**2/(2(k-1)*Ma1**2) def Ma2T(Ma1,k):return 1/Ma1**2*(2/(k1))**2*(k*Ma1**2-(k-1)/2)*(1(k-1)/2*Ma1**2) def…

【陪诊系统】打包问题

使用vite打包后&#xff0c;点击打开index.html是空白的&#xff0c;然后查阅了一些资料&#xff0c;原来是路径问题 【解决办法】&#xff1a;在vite.config.js中去添加base:‘./’ ok,完美解决&#xff0c;再打开打包好后的index.html可以看到界面了

CoppeliaSim和Matlab建立远程连接教程

CoppeliaSim和Matlab建立远程连接教程 Matlab通过调用CoppeliaSim的远程API和库函数实现远程连接,为实现Matlab和CoppeliaSim的联合仿真做准备。 一、获取并查看版本信息 点击 Help 查看版本信息 使用的CoppeliaSim Edu版本为:4.4.0 位数:64bit 二、拷贝API函数和库文件…

Ngx+Lua+Redis 实时IP黑名单系统

实时黑名单系统&#xff0c;如果用php脚本实现很容易&#xff0c;但是效率惨不忍睹呀。 要想速度快还的在nginx层实现阻塞。如果iptables 层阻塞速度更快&#xff0c;但是黑名单列表如果有更新就必须要重载配置&#xff0c;实现还是有难度的。php管理后台把黑名单ip写入到redis…

【数据分享】全国地级市2000-2022年公路里程数据(Shp/Excel格式)

公路里程是表征城市建设的重要指标&#xff01;我们发现在各省市统计年鉴、经济社会发展统计中有公路里程数据&#xff0c;例如下图为2022年南京统计年鉴中统计的2021年和2020年的南京市公路里程数据&#xff1a; 我们特地从2001-2023年各省级/市级年鉴中汇总整理了全国地级市的…

PCL 将点云的曲率数据保存至txt

目录 一、概述 1.1原理 1.2实现步骤 1.3应用场景 二、代码实现 2.1关键函数 2.1.1 计算点云曲率 2.1.2 将曲率保存到txt文件 2.2完整代码 三、实现效果 PCL点云算法汇总及实战案例汇总的目录地址链接&#xff1a; PCL点云算法与项目实战案例汇总&#xff08;长期更新…

【论文速看】DL最新进展20241008-多模态、医学图像分割、扩散模型

目录 【多模态】【医学图像分割】【扩散模型】 【多模态】 PaliGemma: A versatile 3B VLM for transfer 研究机构&#xff1a;google 论文链接&#xff1a;https://arxiv.org/pdf/2407.07726 模型及其说明链接&#xff1a;https://huggingface.co/google/paligemma-3b-pt-22…

leetcode68:文本左右对齐

给定一个单词数组 words 和一个长度 maxWidth &#xff0c;重新排版单词&#xff0c;使其成为每行恰好有 maxWidth 个字符&#xff0c;且左右两端对齐的文本。 你应该使用 “贪心算法” 来放置给定的单词&#xff1b;也就是说&#xff0c;尽可能多地往每行中放置单词。必要时可…

RWKV-7 预览版、大量新论文...RWKV 社区 9 月动态速览

欢迎大家收看《RWKV 社区最新动态》第五期&#xff0c;本期内容收录了 RWKV 社区 2024 年 9 月的最新动态。 9 月动态省流版&#xff08;TL;DR&#xff09; RWKV 官方新闻动态 RWKV-7 发布预览版RWKV-7 论文撰写已面向社区开放RWKV 官网上线 Bad Case 收集页面RWKV 中文文档已…

攻防世界---->sherlock

做题笔记。 下载。 单词中出现大写很可疑。因为大写最多出现在开头等。 猜测是隐写术。 进行筛选。 借助python实现 with open(C:\\Users\\Acer\\Downloads\\f590c0f99c014b01a5ab8b611b46c57c.txt, r) as file:text file.read() uppercase_letters [char for char in text…

手撕数据结构 —— 顺序表(C语言讲解)

目录 1.顺序表简介 什么是顺序表 顺序表的分类 2.顺序表的实现 SeqList.h中接口总览 具体实现 顺序表的定义 顺序表的初始化 顺序表的销毁 打印顺序表 ​编辑 检查顺序表的容量 尾插 尾删 ​编辑 头插 头删 查找 在pos位置插入元素 删除pos位置的值 ​…

内核驱动-如何编译内核以及给内核中添加新文件

1.编译内核 想要编译内核&#xff0c;首先需要先下载内核源代码。可以在官方网站下载源代码压缩包&#xff0c;然后放在Ubuntu的目录下&#xff0c;然后解压&#xff08;解压的指令为&#xff1a;sudo tar -xvf xxxx.gz&#xff09;。解压之后在当前目录下可以看到解压之后的文…

java8 双冒号(::)使用方法

双冒号&#xff08;::&#xff09;运算符是跟函数式接口相关的运算符&#xff0c;作为函数式接口的赋值操作。 双冒号用于静态方法 使用方法&#xff1a;将类的静态方法赋值给一个函数式接口&#xff0c;静态方法的参数个数、类型要跟函数式的接口一致。调用这个函数式接口就…

数字化转型:别让技术迷了眼,战略觉醒才是关键。新媒体营销大客户销售AIGC大模型创新思维专家培训讲师谈数字化转型商业模式短视频内容社私域数字经济人工智能

​数字化转型从根本上讲不是关于技术,而是关于战略。 数字化转型使用新的数字技术来实现重大的业务改进,如增强客户体验、精简运营或创建新的商业模式。数字化转型描述了一家公司试图为数字时代做好准备的旅程。 数字化转型不是关于技术或获取新的技术技能。事实上,它是关于获得…

永磁同步电机环路反步法(backstepping)控制

文章目录 1、反步控制原理1.1 李雅普诺夫稳定性定理1.2 严格反馈系统1.3 一般设计流程 2、永磁同步电机反步控制2.1 反步控制器设计2.2 反步控制仿真 参考 写在前面&#xff1a;本人能力、时间、技术有限&#xff0c;没有对一些细节进行深入研究和分析&#xff0c;也难免有不足…

简易CPU设计入门:取指令(四)

项目代码下载 还是请大家首先准备好本项目所用的源代码。如果已经下载了&#xff0c;那就不用重复下载了。如果还没有下载&#xff0c;那么&#xff0c;请大家点击下方链接&#xff0c;来了解下载本项目的CPU源代码的方法。 下载本项目代码 准备好了项目源代码以后&#xff…

SOMEIP_ETS_174: SD_Unknown_Option_type

测试目的&#xff1a; 验证DUT能够拒绝一个引用了未知选项类型的SubscribeEventgroup消息&#xff0c;并以SubscribeEventgroupNAck作为响应。 描述 本测试用例旨在确保DUT遵循SOME/IP协议&#xff0c;当接收到一个引用了未知选项类型的SubscribeEventgroup消息时&#xff0…

Solidedge二次开发(C#)-将dft文件转换为dwg格式文件

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 1、前言2、在Solid Edge中创建一个par文件3、通过二次开发将dft转换为dwg4、结果显示1、前言 Solid Edge提供了将dft转换为dwg的接口,也即是保存功能。有时在不显示Solid Edge界面的情况下,将其…