深入浅出Java的多线程编程——第二篇

news2024/11/24 10:30:10

目录

前情回顾

1. 中断一个线程

1.1 中断的API

1.2 小结

2. 等待一个线程

 2.1 等待的API

3. 线程的状态

3.1 贯彻线程的所有状态

3.2 线程状态和状态转移的意义

4. 多线程带来的的风险-线程安全 (重点)

4.1 观察线程不安全

4.2 线程安全的概念

4.3 线程不安全的原因

4.3.1 修改共享数据

4.3.2 原子性

4.3.3 可见性

4.3.4 代码顺序性

4.4 解决之前的线程不安全问题


前情回顾

操作系统、进程和线程_木子斤欠木同的博客-CSDN博客

深入浅出Java的多线程编程——第一篇_木子斤欠木同的博客-CSDN博客

让我们来回顾一下,第一篇多线程的内容:

1. 多线程:

(1)线程的概念

(2)进程和线程的区别

(3)Java代码如何创建线程

①继承Thread重写run

②实现Runnable接口重写run,将该实现类作为参数传给Thread的构造方法

③继承Thread,匿名内部类

④实现Runnable,匿名内部类

⑤lambda表达式

2. Thread的常用属性

start方法,真正从系统这里,创建一个线程,新的线程将会执行run方法。

run方法:表示线程的入口方法是啥(线程启动起来,要执行哪些逻辑)(run方法不是让程序猿调用的,要交给系统去自动调用)【换个角度理解:我们可以把线程的run方法理解为main方法,都是系统去自动调用】

1. 中断一个线程

李四一旦进到工作状态,他就会按照行动指南上的步骤去进行工作,不完成是不会结束的。但有时我们需要增加一些机制,例如老板突然来电话了,说转账的对方是个骗子,需要赶紧停止转账,那张三该如何通知李四停止呢?这就涉及到我们的停止线程的方式了。

本质上来说,让一个线程终止,办法就一种,让该线程的入口方法执行完毕!也就是让run跑完!

目前常见的有以下两种方式:

  • 通过共享的标记来进行沟通
  • 调用 interrupt() 方法来通知【记住,只是通知而已】

示例1:使用自定义的变量来作为标志位

  • 需要给标志位上加 volatile 关键字(这个关键字的功能后面介绍).
    public static volatile boolean isQuit = false;

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(()->{
            while (!isQuit){
                System.out.println(Thread.currentThread().getName() + " : 别管我,我忙着转正!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + " : 啊!险些误了大事");
        });
        System.out.println(Thread.currentThread().getName() + " : 让李四开始转账");
        t.start();
        Thread.sleep(10*1000);
        System.out.println(Thread.currentThread().getName() + " : 老板来电话了,得赶紧通知李四对方是个骗子!");
        isQuit = true;
    }

这里的   public static volatile boolean isQuit = false; 为什么需要加static,因为main函数被static修饰,所以在main内部用到的成员变量要加static

示例2:使用 Thread.interrupted() 或者 Thread.currentThread().isInterrupted() 代替自定义标志位。

1.1 中断的API

  • Thread 内部包含了一个 boolean 类型的变量作为线程是否被中断的标记。
方法说明
pubilc void interrupt()中断对象关联的线程,如果线程正在阻塞,会把线程唤醒,则以异常的方式通知,然后吧标志位设置为true
public static boolean interrupted()判断当前线程的中断标志位是否设置,调用后清除标志位
public boolean isInterrupted()判断对象关联的线程的标志位是否设置,调用后不清除标志位
  • 使用 thread 对象的 interrupted() 方法通知线程结束.
public class Thread4 {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
           while(!Thread.currentThread().isInterrupted()){
               System.out.println("hello t");
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
        });
        t.start();
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t.interrupt();
    }
}

我们可以发现,调用t.interrupt方法的时候,线程并没有真的结束,而是打印了个异常信息,又继续执行了

1.2 小结

interrupt方法的作用:

(1)设置标志位为true

(2)如果该线程正在阻塞中(比如执行了sleep)此时就会把阻塞状态唤醒,通过抛出异常的方式让sleep立即结束

注意:一个非常重要的问题,当sleep被唤醒的时候(只有sleep被唤醒才会重置标志位),sleep自动地把isInterrupted标志位给清空了(true - > false),这导致下次循环,循环仍然可以继续执行了~~

有的开关,是按下之后,就按下去了

有的开关,是按下去之后,自动弹起(sleep就属于这种)

一种极端的情况:

如果设置interrupt的时候,恰好sleep刚醒,这个时候赶巧了,执行到下一轮循环条件就直接结束了。但是这种概率非常低,毕竟sleep的时间已经占据了整个循环体的99.999%的时间了

如果需要结束循环,就得在catch中搞个break

public class Thread4 {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
           while(!Thread.currentThread().isInterrupted()){
               System.out.println("hello t");
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   e.printStackTrace();
                   break;
               }
           }
        });
        t.start();
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t.interrupt();
    }
}

小结一下:

如果sleep执行的时候看到这个标志位是false,sleep正常进行休眠操作

如果当前的标志位为true

sleep无论是刚刚执行还是已经执行了一般,都会触发两件事

(1)立即抛出异常

(2)清空标志位为false 

再下次循环,到sleep

由于当前标志位本身是false,就啥也不干~~

总结到这里,就有小伙伴有疑问了,为什么sleep要清空标志位呢?

目的就是为了让线程自身能够对于线程何时结束,有一个更明确的控制~~

当前,interrupt方法,效果不是让线程立即结束,而是告诉他,你该结束了,至于他是否真的要立即结束还是等会结束,都是由它本线程的代码来控制~~,interrupt只是通知,而非“命令”!

有的朋友就好奇了,我如果不加sleep,这些能令线程阻塞的代码,那是不是就能让该线程直接结束呢?对的,是可以,但是工作中没人会这么写代码,一个不可控的线程是多么可怕!

这里就可以又引出一个问题,java为啥不强制制定“命令结束”的操作呢?

只要调用interrupt就立即结束?

答:主要是设定成这种,对线程来说非常不友好~,线程t何时结束,一定是t自己要最清楚,交给t自身来决定比较好!

2. 等待一个线程

有时,我们需要等待一个线程完成它的工作后,才能进行自己的下一步工作。例如,张三只有等李四转账成功,才决定是否存钱,这时我们需要一个方法明确等待线程的结束。

public class Thread5 {
    public static void main(String[] args) throws InterruptedException {
        Runnable run = () -> {
          for(int i = 0;i < 3;i++){
              try {
                  System.out.println(Thread.currentThread().getName() + " : 我正在工作!");
                  Thread.sleep(1000);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
            System.out.println(Thread.currentThread().getName() + " : 我的工作结束了!");
        };

        Thread t1 = new Thread(run,"李四");
        Thread t2 = new Thread(run,"张三");
        System.out.println("李四先工作");
        t1.start();
        t1.join();
        System.out.println("李四做完了,张三开始工作!");
        t2.start();
        t2.join();
        System.out.println("王五做完了,张三开始工作!");
        System.out.println("两人都工作结束了!");
    }
}

在main线程中调用t1.join()表示main线程要等t1线程跑完,main线程才可以继续执行。

(1)main线程调用t1.join()的时候,如果t1还在运行,此时main线程阻塞,知道t执行完毕(t1的run执行完了),main线程才从阻塞中解除,才继续执行。

(2)main线程调用t.join()的时候,如果t已经结束了,此时join就不会阻塞,会立即执行下去。

如果把两个join方法注释掉,就会CPU的抢占式调用的典型例子:

public class Thread5 {
    public static void main(String[] args) throws InterruptedException {
        Runnable run = () -> {
          for(int i = 0;i < 3;i++){
              try {
                  System.out.println(Thread.currentThread().getName() + " : 我正在工作!");
                  Thread.sleep(1000);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
            System.out.println(Thread.currentThread().getName() + " : 我的工作结束了!");
        };

        Thread t1 = new Thread(run,"李四");
        Thread t2 = new Thread(run,"张三");
        System.out.println("李四先工作");
        t1.start();
//        t1.join();
        System.out.println("李四做完了,张三开始工作!");
        t2.start();
//        t2.join();
        System.out.println("王五做完了,张三开始工作!");
        System.out.println("两人都工作结束了!");
    }
}

 2.1 等待的API

方法说明
public void join()等待线程结束
public void join(long millis)等待线程结束,最多等 millis 毫秒
public void join(long millis, int nanos)同理,但可以更高精度

3. 线程的状态

3.1 贯彻线程的所有状态

线程的状态是一个枚举类型 Thread.State

public class Thread6 {
    public static void main(String[] args) {
        for (Thread.State state:Thread.State.values()) {
            System.out.println(state);
        }
    }
}

  • NEW: 安排了工作, 还未开始行动
  • RUNNABLE: 可工作的. 又可以分成正在工作中和即将开始工作.
  • BLOCKED: 这几个都表示排队等着其他事情
  • WAITING: 这几个都表示排队等着其他事情
  • TIMED_WAITING: 这几个都表示排队等着其他事情
  • TERMINATED: 工作完成了.

3.2 线程状态和状态转移的意义

大家不要被这个状态转移图吓到,我们重点是要理解状态的意义以及各个状态的具体意思。

举个栗子:
       刚把李四、王五找来,还是给他们在安排任务,没让他们行动起来,就是 NEW 状态;
当李四、王五开始去窗口排队,等待服务,就进入到 RUNNABLE 状态。该状态并不表示已经被银行工。
       作人员开始接待,排在队伍中也是属于该状态,即可被服务的状态,是否开始服务,则看调度器的调度;
       当李四、王五因为一些事情需要去忙,例如需要填写信息、回家取证件、发呆一会等等时,进入
        BLOCKED 、 WATING 、 TIMED_WAITING 状态,至于这些状态的细分,我们以后再详解;
如果李四、王五已经忙完,为 TERMINATED 状态。所以,之前我们学过的 isAlive() 方法,可以认为是处于不是 NEW 和 TERMINATED 的状态都是活着的。

4. 多线程带来的的风险-线程安全 (重点)

本质是因为线程之间的调度顺序的不确定性

4.1 观察线程不安全

public class Thread7 {
    static int count = 0;


    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            for(int i = 0;i < 5000;i++){
                count++;
            }
        });
        Thread t2 = new Thread(() -> {
            for(int i = 0;i < 5000;i++){
                count++;
            }
        });
        t1.start();
        t2.start();
        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(count);
    }

}

 

由于当前这两线程调度的顺序是无序的~~

你也不知道这两线程自增的过程中,到底经历了什么

有多少次是“顺序执行”,有多少次是“交错执行”不知道!

得到的结果是啥也就是不确定的!

这里就引出了一个问题,出现bug之后,得到的结果一定是 <= 10000,或者结果一定是 >= 5000?

CPU调用是以原语为单位的!

4.2 线程安全的概念

想给出一个线程安全的确切定义是复杂的,但我们可以这样认为:
如果多线程环境下代码运行的结果是符合我们预期的,即在单线程环境应该的结果,则说这个程序是线程安全的。

4.3 线程不安全的原因

4.3.1 修改共享数据

上面的线程不安全的代码中, 涉及到多个线程针对 counter.count 变量进行修改.
此时这个 count 是一个多个线程都能访问到的 "共享数据"!

count 这个变量就是在堆上. 因此可以被多个线程共享访问. 

4.3.2 原子性

什么是原子性
我们把一段代码想象成一个房间,每个线程就是要进入这个房间的人。如果没有任何机制保证,A进入房间之后,还没有出来;B 是不是也可以进入房间,打断 A 在房间里的隐私。这个就是不具备原子性的。
那我们应该如何解决这个问题呢?是不是只要给房间加一把锁,A 进去就把门锁上,其他人是不是就进不来了。这样就保证了这段代码的原子性了。有时也把这个现象叫做同步互斥,表示操作是互相排斥的。 

一条 java 语句不一定是原子的,也不一定只是一条指令

比如刚才我们看到的 n++,其实是由三步操作组成的:

  1. 从内存把数据读到 CPU
  2. 进行数据更新
  3. 把数据写回到 CPU

不保证原子性会给多线程带来什么问题
如果一个线程正在对一个变量操作,中途其他线程插入进来了,如果这个操作被打断了,结果就可能是错误的。

这点也和线程的抢占式调度密切相关. 如果线程不是 "抢占" 的, 就算没有原子性, 也问题不大。

4.3.3 可见性

可见性指一个线程对共享变量值的修改,能够及时地被其他线程看到。

Java 内存模型 (JMM): Java虚拟机规范中定义了Java内存模型.
目的是屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的并发效果.

  1. 线程之间的共享变量存在 主内存 (Main Memory).
  2. 每一个线程都有自己的 "工作内存" (Working Memory) .
  3. 当线程要读取一个共享变量的时候, 会先把变量从主内存拷贝到工作内存, 再从工作内存读取数据.
  4. 当线程要修改一个共享变量的时候, 也会先修改工作内存中的副本, 再同步回主内存. 

由于每个线程有自己的工作内存, 这些工作内存中的内容相当于同一个共享变量的 "副本". 此时修改线程1 的工作内存中的值, 线程2 的工作内存不一定会及时变化.

1) 初始情况下, 两个线程的工作内存内容一致.

2) 一旦线程1 修改了 a 的值, 此时主内存不一定能及时同步. 对应的线程2 的工作内存的 a 的值也不一定能及时同步. 

这个时候代码中就容易出现问题.

此时引入了两个问题: 

  • 为啥要整这么多内存?
  • 为啥要这么麻烦的拷来拷去?

1) 为啥整这么多内存?
实际并没有这么多 "内存". 这只是 Java 规范中的一个术语, 是属于 "抽象" 的叫法.
所谓的 "主内存" 才是真正硬件角度的 "内存". 而所谓的 "工作内存", 则是指 CPU 的寄存器和高速缓存.

2) 为啥要这么麻烦的拷来拷去?
因为 CPU 访问自身寄存器的速度以及高速缓存的速度, 远远超过访问内存的速度(快了 3 - 4 个数量级, 也就是几千倍, 上万倍)

比如某个代码中要连续 10 次读取某个变量的值, 如果 10 次都从内存读, 速度是很慢的. 但是如果只是第一次从内存读, 读到的结果缓存到 CPU 的某个寄存器中, 那么后 9 次读数据就不必直接访问内存了,效率就大大提高了

那么接下来问题又来了, 既然访问寄存器速度这么快, 还要内存干啥??
答案就是一个字: 贵

值的一提的是, 快和慢都是相对的. CPU 访问寄存器速度远远快于内存, 但是内存的访问速度又远远快于硬盘。
对应的, CPU 的价格最贵, 内存次之, 硬盘最便宜。

4.3.4 代码顺序性

什么是代码重排序
一段代码是这样的:

  1. 去前台取下 U 盘
  2. 去教室写 10 分钟作业
  3. 去前台取下快递

如果是在单线程情况下,JVM、CPU指令集会对其进行优化,比如,按 1->3->2的方式执行,也是没问题,可以少跑一次前台,这种叫做指令重排序。

编译器对于指令重排序的前提是 "保持逻辑不发生变化". 这一点在单线程环境下比较容易判断, 但是在多线程环境下就没那么容易了, 多线程的代码执行复杂程度更高, 编译器很难在编译阶段对代码的执行效果进行预测, 因此激进的重排序很容易导致优化后的逻辑和之前不等价.

重排序是一个比较复杂的话题, 涉及到 CPU 以及编译器的一些底层工作原理, 此处不做过多讨论

4.4 解决之前的线程不安全问题

public class Thread7 {
    static int count = 0;


    public static void main(String[] args) {
        Object o = new Object();
        Thread t1 = new Thread(() -> {

            synchronized (o) {
                for(int i = 0;i < 5000;i++){
                    count++;
                }
            }
        });
        Thread t2 = new Thread(() -> {
            synchronized (o) {
                for(int i = 0;i < 5000;i++){
                    count++;
                }
            }
        });
        t1.start();
        t2.start();
        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(count);
    }

}

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

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

相关文章

信息安全:网络安全漏洞防护技术原理与应用.

信息安全&#xff1a;网络安全漏洞防护技术原理与应用. 网络安全漏洞又称为脆弱性&#xff0c;简称漏洞。漏洞一般是致使网络信息系统安全策略相冲突的缺陷&#xff0c;这种缺陷通常称为安全隐患。 安全漏洞的影响主要有机密性受损、完整性破坏、可用性降低、抗抵赖性缺失、可…

中睿天下精彩亮相2023国家网络安全宣传周河南省活动

9月11日&#xff0c;2023年国家网络安全宣传周河南省活动正式拉开帷幕&#xff0c;开幕式在八朝古都开封举行。中睿天下受邀参与本次盛会并亮相网络安全产品展&#xff0c;重点展示在网络安全攻击溯源领域的前沿技术、创新方案及实践应用&#xff0c;为与会嘉宾带来了一场独具优…

STL算术生成和集合算法

目录 算术生成算法accumulate 算术生成算法file 常用集合算法 常用集合算法 常用集合算法set_difference 算术生成算法accumulate 算术生成算法属于小型算法&#xff0c;使用时包含的头文件为 include <numeric> accumulate(iterator beg, iterator end, value); …

13:STM32----PWR

目录 一:PWR 1:简历 2:电源框图 3:低功耗模式 4:模式选择 5:低功耗模式注意事项 A:睡眠模式 B:停止模式 C:待机模式 二 : 案例 A:修改主频 1:连接图 2:代码 B:睡眠模式串口发送接收 1:连接图 2:代码 C:停止模式对射式红外传感器计次 1:连接图 2:函数介绍​…

C++入门(前言、命名空间、缺省参数、重载)

该篇博客主要讲述关于C发展前言&#xff0c;以及C祖师爷对C进行改造提升后的新语法进行解释说明&#xff0c;具体内容为&#xff1a;发展历程、命名空间、输入\输出、缺省参数、函数的重载 目录 文章目录 前言 1.什么是C 2.C的发展史 3.关于C的学习 一、关于命名空间 1.含义 2.…

JMeter测试Web服务

Web服务简介 什么是Web服务 【Web服务】即Web Service。Web服务是一种服务导向架构的技术&#xff0c;通过标准的Web协议提供服务&#xff0c;目的是保证不同平台的应用服务可以互操作。根据W3C的定义&#xff0c;Web服务&#xff08;Web service&#xff09;应当是一个软件系统…

ElasticSearch - 基于 “黑马旅游” 案例,实现搜索框、分页、条件过滤、附近酒店、广告置顶功能

目录 一、黑马旅游案例 1.1、实现 搜索框 和 分页 功能 1.1.1、需求分析 a&#xff09;首先搜索框需求 b&#xff09;分页需求 1.1.2、定义实体类 1.1.2、定义 controller 1.1.3、注入 RestHighLevelClient 1.1.4、实现 IHotelService 接口的 search 方法 1.1.5、功能…

【OLSR路由协议】链路状态路由(OLSR)协议中选择多点中继节点算法研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

AIGC 绘画Stable Diffusion工具的安装与使用

我们先让ChatGPT来帮我们回答一下,什么是Stable Diffusion Stable Diffusion 是一种基于概率模型的图像生成技术。它通过对图像空间中每个像素的颜色值进行推断,从而生成具有高度真实感和细节的图像。 Stable Diffusion 使用一种称为扩散过程的方法来生成图像。在生成过程中…

Gd-DOTA,CAS:72573-82-1,钆特酸

产品简介&#xff1a;螯合剂是金属原子或离头子与樤含有两个或连两个以上配位原子的配位体作用&#xff0c;生成具有环状结构的络合物&#xff0c;该络合物叫做螯合物。能生成螯合物的这种配体物质叫螯合剂&#xff0c;也成为络合剂。 CAS号&#xff1a;72573-82-1 中文名&am…

基于VR元宇宙技术搭建林业生态模拟仿真教学系统

随着科技的飞速发展&#xff0c;教学方式也正在经历着巨大的变革。林业经济学元宇宙虚拟教学系统作为一种新兴的教学方式&#xff0c;为学生和教师提供了一个全新的、沉浸式的学习和教学环境。 森林管理和监测 元宇宙技术可以用于森林管理和监测。通过无人机、传感器和虚拟现实…

缓冲区溢出漏洞分析

一、实验目的 熟悉软件安全需求分析方法&#xff0c;掌握软件安全分析技术。 二、实验软硬件要求 1、操作系统&#xff1a;windows 7/8/10等 2、开发环境&#xff1a;VS 6.0&#xff08;C&#xff09;、OllyDbg 三、实验预习 《软件安全技术》教材第3章 四、实验内容&#…

中间相遇法(分治类问题非等大分治的平衡做法)

分治&#xff0c;如果分成两半大小不一样&#xff0c;很容易被卡到 O ( n 2 ) O(n^2) O(n2) 在某些题目中&#xff0c;利用中间相遇法&#xff0c;我们可以优化这个过程 其优化的前提是分治的大头在找分界点 复杂度不用证&#xff0c;很好理解吧 这层找地越久&#xff0c;下…

常识判断 --- 科技常识

目录 力与热 光和声 航空成就 垃圾分类 百科知识 血型 二十四节气歌 春雨惊春清谷天 夏满忙夏暑相连 秋处露秋寒霜降 冬雪雪冬小大寒 力与热 光和声 航空成就 垃圾分类 百科知识 血型

机器人中的数值优化|【四】L-BFGS理论推导与延伸

机器人中的数值优化|【四】L-BFGS理论推导与延伸 往期内容回顾 机器人中的数值优化|【一】数值优化基础 机器人中的数值优化|【二】最速下降法&#xff0c;可行牛顿法的python实现&#xff0c;以Rosenbrock function为例 机器人中的数值优化|【三】无约束优化&#xff0c;拟牛…

丰田的国际化与转型困境:对中国车企的欧洲策略启示

摘要&#xff1a;欧洲市场的消费者汽车偏好多样,中国车企进军欧洲时,需考虑产品设计和当地法规。回顾历史,丰田汽车通过其独特管理理念,在美国从廉价品牌形象成功转型为高质量、受信赖的全球品牌。但进入电动汽车时代&#xff0c;日本车企因深度共生的传统供应链而转型坎坷。中…

如何使用ES6+特性进行现代前端开发?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 使用ES6 特性进行现代前端开发1. 使用 let 和 const2. 使用箭头函数3. 使用模板字符串4. 使用解构赋值5. 使用类和模块6. 使用扩展运算符和剩余参数7. 异步编程8. 使用模块打包工具9. 使用现代浏览器 ⭐ 写在最后 ⭐ 专栏简介 前端入门之…

ChatGPT推出全新功能,引发人工智能合成声音担忧|百能云芯

人工智能AI科技企业OpenAI公司25日宣布&#xff0c;其聊天应用程序ChatGPT如今具备「看、听、说」能力&#xff0c;至少能够理解口语、用合成语音回应并且处理图像&#xff1b;但专家忧心&#xff0c;以假乱真与深度伪造的乱象可能变本加厉。 国家广播公司新闻网(NBC News)报导…

1.算法——数据结构学习

算法是解决特定问题求解步骤的描述。 从1加到100的结果 # include <stdio.h> int main(){ int i, sum 0, n 100; // 执行1次for(i 1; i < n; i){ // 执行n 1次sum sum i; // 执行n次} printf("%d", sum); // 执行1次return 0; }高斯求和…

孙哥Spring源码第26集

第26集、AnnotationAwareAspectJAutoProxyCreator源码 【视频来源于&#xff1a;B站up主孙帅suns Spring源码视频】【微信号&#xff1a;suns45】 26.1、postProcessAfterInitialization分析 26.2、wrapIfNecessary分析 26.3、createProxy分析 26.4、getProxy 26.5、BeanPost…