Java进程线程介绍创建和执行销毁并理解线程安全和线程池 Native Method

news2024/9/23 18:33:34

目录

  • 1.进程和线程
  • 2.多线程的核心
  • 3.操作系统的多任务--以非常小的时间间隔交替执行
  • 4.native 修饰的方法
  • 5.Thread
    • 创建线程的两种方式
      • 1.普遍采用实现Runnable接口的方式
      • 2.继承Thread方式
  • 6.自定义线程用 new Thread(Runnable target) 启动源码分析
      • 6.1-new Thread(myThread)
      • 6.2对实例 myThread 创建线程 并给 Thread类 成员变量 target 赋予当前对象实例
  • 7.实现接口Runnable和继承Thread类的区别
  • 8.启动线程通过start()和 run () 区别
  • 9.Thread类target
  • 10.1线程之一经典的银行转账线程问题
  • 10.2线程之二 经典的买火车票问题
  • 11工作中,几乎所有的多线程应用都用实现Runnable这种方式
  • 12关于线程安全问题
      • 多线程共享数据时的问题
      • 解决之道--思想--线程同步
      • 解决之道--具体实现
  • 13.死锁
  • 14.线程间的通信
  • 15.同步实现方式--线程的状态

1.进程和线程

进程和线程都是一个控制流程。
一个进程通常对应于一个程序。
一个程序可以由多个不同的线程构成。
一个进程就是一个应用程序
一个应用程序面对多个用户则多个进程
一个进程则多个线程
多个线程共享资源且携带数据操作
多进程资源隔离

2.多线程的核心

多线程的核心在于多个代码块并发执行,本质特点在于各代码块之间的代码是乱序执行的。

3.操作系统的多任务–以非常小的时间间隔交替执行

进程:正在进行的程序
现在使用的操作系统都是多任务的,即能够 同时 执行多个应用程序。实际是操作系统负责对CPU等设备资源进行分配和管理,虽然这些设备某一时刻只能做一件事情,但以非常小的时间间隔交替执行多个程序,就给人同时执行多个程序的感觉

4.native 修饰的方法

Native Method 调用java外的程序包,其中包含
一个Native Method就是一个java调用非java代码的接口
一个native method方法可以返回任何java类型,包括非基本类型,而且同样可以进行异常控制。

5.Thread

一个Thread类的对象代表一个线程,而且只能代表一个线程。
通过Thread类和它定义的对象,我们能获得当前线程对象、获取某一线程的名称、可以实现控制线程暂停一段时间等功能
在这里插入图片描述

创建线程的两种方式

共同点都是自定义线程类重写run()方法 并且通过 new Thread (线程对象的实例).start() 方法启动线程和创建一个实例线程启动运行run()方法,真正实现多线程的方式实现

1.普遍采用实现Runnable接口的方式

public class MyThreadImp implements Runnable {
    @Override
    public void run() {
        System.out.println("执行实现Runable接口的MyThreadImp的类中run()方法");
    }

    public static void main(String[] args) {
        MyThreadImp myThreadImp = new MyThreadImp();
        Thread thread =new Thread(myThreadImp);
        thread.start();//执行实现Runable接口的MyThreadImp的类中run()方法
    }
}

2.继承Thread方式

public class MyThread extends  Thread{
	//等待线程调用
    public  void  run (){
        System.out.println("执行了Mythread的run() 方法");
        System.out.println(currentThread());
    }
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        Thread thread  = new Thread(myThread);
        thread.start();// 执行了Mythread的run() 方法
    }
}

6.自定义线程用 new Thread(Runnable target) 启动源码分析

6.1-new Thread(myThread)

主要
1启动线程;
thread.start();
private native void start0(); 调用外部接口启动线程 可能涉及到 硬件或者动态链接库函数等待

 public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }

6.2对实例 myThread 创建线程 并给 Thread类 成员变量 target 赋予当前对象实例

创建实例的线程(如果必要可以给线程等级,权限)
/* What will be run. */ 运行哪一个对象实例
private Runnable target;


 public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }
    
private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
        init(g, target, name, stackSize, null, true);
    }

 private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }

        this.name = name;
        //创建线程
        Thread parent = currentThread()
 		。。。。。。
       //  成员变量 target 赋予当前对象实例
        this.target = target;
        //获取守护进程
        this.daemon = parent.isDaemon();
        //获取进程权限
        this.priority = parent.getPriority();
		。。。。。。
		//省略部分代码
		。。。。。。
        /* Set thread ID */
        //获取下个线程id
        tid = nextThreadID();
    }

7.实现接口Runnable和继承Thread类的区别

①处理和共享同一资源对多个相同的程序代码的线程
②避免由于java单继承特征带来的局限性
③增强代码的健壮性和灵活性可以多个线程共享,代码和数据是独立的

8.启动线程通过start()和 run () 区别

start方法:
通过该方法启动线程的同时也创建了一个线程.真正实现了多线程。并不等待run方法法中的代码执行完毕,就可以接着执行下面的代码。此时start0的这个线程处于就绪状态.等待到CPU的时间片后就会执行其史的run方法。.这个run方法 含了要执行的这个线程的内容, run()方法运行结束,此线程也就终止了。
run方法:
通过run方法启动线程其实就是调用一个类中的方法,当作普通的方法的方式调用。并没有创建一个线程,程序中依旧只有一个主线程,必须等到run()方法里面的代码执行完毕,才会继续执行下面的代码,这样就没有达到写线程的目的。

 public synchronized void start() {
        if (threadStatus != 0)
            throw new IllegalThreadStateException();
        group.add(this);
        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
            }
        }
    }
    private native void start0();

当一个start()线程启动的时候,它的状态〈threadStatus)被设置为О(即线程为NEW状态),如果不为0,则抛出llegalThreadStateException异常。
正常的话,将该线程加入线程组,最后尝试调用start0方法,而start0方法是私有的native方法(Native Method是一个java调用非java代码的接口)。
start调用后,线程出于就绪状态RUNNABLE,等分配到cpu时间片时系统会调用thread的run()方法,而这个方法其实只是调用runnable里面自己实现的run()方法。


public class Test7Thread extends  Thread{
    //
    public  void  run (){
        System.out.println("执行了Test7Thread");
        System.out.println(currentThread());
    }

    public static void main(String[] args) {
        Test7Thread  test7Thread = new Test7Thread();
        test7Thread.start();//执行了Test7Thread
    }

}
public class test5 {
    public static void main(String[] args) {
        Thread thread  = new Thread(){
            public  void  run(){
                pong();
            }
        };
        thread.start();//启动线程start方法  启动run方法
        thread.run();//启动run方法  启动线程start方法
        System.out.println("启动线程start方法");
    }

    static void pong (){
        System.out.println("启动run方法");
    }
}

9.Thread类target

target - 其 run 方法被调用的对象。
首先run方法是接口 Runnable 中,而Thread 实现此接口并重写run方法
与此同时创建创建新执行线程有两种方法。
一种方法是将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法。接下来可以分配并启动该子类的实例。
创建线程的另一种方法是声明实现 Runnable 接口的类。该类然后实现 run 方法。然后可以分配该类的实例,在创建 Thread 时作为一个参数来传递并启动。采用这种风格的同一个例子如下所示:
不管是继承还是实现接口的都是调用了run() 方法 ,谁去调用的??是创建的类的对象调用,
故 target - 其 run 方法被调用的对象。

Thread类中
 @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }

10.1线程之一经典的银行转账线程问题

有一个银行账户,还有余额1100元,现在A通过银行卡从中取1000元,而同时另外一个人B通过存折也从这个账户中取1000元。取钱之前,要首先进行判断:如果账户中的余额大于要取的金额,则可以执行取款操作,否则,将拒绝取款。
我们假定有两个线程来分别从银行卡和存折进行取款操作,当A线程执行完判断语句后,获得了当前账户中的余额数(1000元),因为余额大于取款金额,所以准备执行取钱操作(从账户中减去1000元),但此时它被线程B打断,然后,线程B根据余额,从中取出1000元,然后,将账户里面的余额减去1000元,然后,返回执行线程A的动作,这个线程将从上次中断的地方开始执行:也就是说,它将不再判断账户中的余额,而是直接将上次中断之前获得的余额减去1000。此时,经过两次的取款操作,账户中的余额为100元,从账面上来看,银行支出了1000元,但实际上,银行支出了2000元。

10.2线程之二 经典的买火车票问题

方法一

=========继承Thread==========模拟4个售票窗口共同卖100张火车票的程序=============
public class TicketThread {
    public static void main(String[] args){
        // 执行继承Thread的线程
        new MyThread().start();//售票窗口1
        new MyThread().start();//售票窗口2
        new MyThread().start();//售票窗口3
        new MyThread().start();//售票窗口4
    }
}

class MyThread extends Thread {
    // 车票数量
    private int tickets = 100;

    public void run() {
        while (tickets > 0) {
            System.out.println(this.getName() + " 卖出第 " + tickets-- + "张火车票.");
        }
    }
}

方法二

==========实现Runnable接口=========模拟4个售票窗口共同卖100张火车票的程序=============
public class TicketRunnable {
    public static void main(String[] args) {
        //实现Runnable接口实现类
        myRunnable myR = new myRunnable();
        new Thread(myR).start();
        new Thread(myR).start();
        new Thread(myR).start();
        new Thread(myR).start();
    }
}
class myRunnable implements Runnable {
    //火车票数量
    private  int tickets = 100; 
    public void run() {
        while (tickets > 0) {
            System.out.println(Thread.currentThread().getName()+"卖出第["+(tickets--) +"]张火车票.");
        }
    }
}

11工作中,几乎所有的多线程应用都用实现Runnable这种方式

把虚拟CPU(线程)同程序的代码、数据有效的分离
健壮、灵活
编写简单,可以直接操纵线程,无需使用Thread.currentThread()。

12关于线程安全问题

多线程共享数据时的问题

在售票程序中,System.out.println(Thread.currentThread().getName()+“卖出第[”+(tickets–) +“]张火车票.”);这段代码执行时候,极有可能出现一种意外。就是同一张票可能会被打印两次或多次,也可能出现打印0或负数的情况。
假设:当tickets的值为1的时候,线程1刚执行完if(tickets>0),正准备执行下面的代码,这时,操作系统将CPU切换到了线程2上执行,此时tickets的值仍是1,线程2执行完以下代码,CPU切回线程1,
线程1不再执行if(tickets>0),继续往下执行,此时意外出现。
可以使用sleep()来制造线程中这种切换的错误。
这种错误也就是我们常说的"线程安全

解决之道–思想–线程同步

多个线程都想对同一个资源或代码块的时,此时此刻只能被单独一个线程执行完毕再释放出去待下一线程继续使用这个代码块,这就是线程同步。
比如:一张火车票只有可能出售给一位旅客,下一个旅客只能等待下一个线程继续操作申请另一张票

解决之道–具体实现

将具有原子性的代码放入synchronize语句内,形成同步代码块。就可以保证线程安全了。
每个对象都有一个标志位(锁旗标),该标志位有两个状态0、1,其开始状态为1,当执行synchronized(Object)语句后,Object对象的标志位变为0状态,直到执行完整个synchronized语句中的代码块后才又回到1状态。一个线程执行到synchronized(Object)的时候,先检查Object对象的标志位,0表示有线程在执行,这个线程将暂时阻塞,让出CPU资源,直到另外线程执行完有关代码,将Object对象状态恢复到1状态,这个阻塞才被取消,然后线程开始执行,同时将Object对象的标志位变为0,防止其他线程再进入有关的同步代码块中。
当线程执行到synchronized的时候,如果得不到锁标记,这个线程会被加入到一个与该对象的锁标记相关连的等待线程池当中,等待锁标记。当线程执行完同步代码,就会释放锁标记。一个刚释放了锁标记的线程可以马上再次获得锁标记,当同步块遇到break或抛出exception时,线程也会释放锁标记。

wait、notify、notifyAll这三个方法只能在synchronized方法中调用,即无论线程调用一个对象的wait还是notify方法,该线程必须先得到该对象的锁旗标,这样,notify只能唤醒同一对象监视器中调用wait的线程。

所以,如果确定程序没有安全性的问题,就没有必要使用同步。

ArrayList    Vector(线程同步)
同步是以牺牲程序的性能为代价的。
所以,如果确定程序没有安全性的问题,就没有必要使用同步。
synchronized还可以作用在方法上面,用来实现线程的同步
同步块越短越好,锁力度过粗容易导致同步严重,力度过细容易发生死锁问题
Synchronized(this){--粗
	System.out.println("hello");
	System.out.println("world");
}
--------------------------------
Synchronized(this){--细
	System.out.println("hello");
}
System.out.println("world");
--------------------------------
如果是静态方法则没有实例,则锁类对象this.getClass
一个类不管有多少个实例,只有一个类对象.
	Synchronized(this.getClass()){}

13.死锁

是指两个线程,都相互等待对方释放lock
是不可测知或避开的
应采取措施避免死锁的出现

14.线程间的通信

Object 类定义了 wait()、notify() 和 notifyAll() 方法。可以让线程相互通知事件的发生。要执行这些方法,必须拥有相关对象的锁。
wait() 会让调用线程休眠,直到用 Thread.interrupt() 中断它 或者wait经过了指定的时间 或者另一个线程用 notify() 或 notifyAll() 唤醒它。
当对某个对象调用 notify() 时,如果有任何线程正在通过 wait() 等待该对象,那么就会唤醒其中一个线程。当对某个对象调用 notifyAll() 时,会唤醒所有正在等待该对象的线程。
wait、notify、notifyAll这三个方法只能在synchronized方法中调用,即无论线程调用一个对象的wait还是notify方法,该线程必须先得到该对象的锁旗标,这样,notify只能唤醒同一对象监视器中调用wait的线程。

package com.thread;

class Product{
    private String name;
    private String price;
    private int count;
    public void setName(String name){
        this.name = name;
    }
    public String getName(){
        return this.name;
    }
    public void setPrice(String price){
        this.price = price;
    }
    public String getPrice(){
        return this.price;
    }
    public void setCount(int count){
        this.count = count;
    }
    public int getCount(){
        return this.count;
    }
    public String toString(){
        return "产品名称:"+this.name+" 价格:"+this.price+" 数量:"+this.count;
    }

    private boolean flag = false;
    public synchronized void put(String name,String price){
        try{
            if(flag){
                //如果有产品就不继续生产
                wait();
            }
            System.out.print("***生产者开始生产-->");
            this.setName(name);
            this.setPrice(price);
            //生产产品数量加1
            this.setCount(this.getCount()+1);
            System.out.println(this);
            flag = true;//生产完毕更改标志
            notify();//通知消费者消费
        }catch(Exception e){
           e.printStackTrace(); 
        }
    }

    public synchronized void get(){
        try{
            if(!flag){
                wait();
               }
            System.out.print("--消费者开始消费产品-->");
            //消费一个产品数量减 1
            this.setCount(this.getCount()-1);
            System.out.println(this);
            flag = false;//消费完,更改标志;
            notify();//通知生产者继续生产
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

class Producter implements Runnable{
    private Product p;
    public Producter(Product p){
        this.p = p;
    }
    public void run(){
        int i=0,n=0;
        while(n<5){
            n++;
            if(i==0){
                p.put("包子","1");
            }else{
                p.put("馒头","2");
            }
            //实现交替生产效果
            i = (i+1)%2;
        }
    }
}

class Consumer implements Runnable{
    private Product p;
    public Consumer(Product p){
        this.p = p;
    }
    public void run(){
        int n = 0;
        while(n<5){
            n++;
            p.get();
        }
    }
}

public class TestThread{
    public static void main(String[] args){
        Product p = new Product();
        new Thread(new Producter(p)).start();
        new Thread(new Consumer(p)).start();
    }
}

15.同步实现方式–线程的状态

synchronized,wait和notify,notityAll。

在这里插入图片描述

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

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

相关文章

mysql8.0.21安装配置方法图文教程

记录了mysql 8.0.21 的安装配置方法&#xff0c;分享给大家。 一、下载 1、下载安装包 mysql下载路径 2、解压压缩包 3、在此目录下新建my.ini配置文件 [mysqld] # 设置 3306 端口 port3306 # 设置 mysql 的安装目录 basedirD:\mysql-8.0.21-winx64 # 设置 mysql 数据…

破案了!不会讲笑话不会作诗的chatGPT!

热出圈的chatGPT, 必须亲手试试热出圈的chatGPT, 必须亲手试试1 猜猜我是谁2 问网传图片李白风格注释代码3 写个程序看看4 帮我猜猜世界杯&#xff08;发了发了&#xff0c;偷笑脸&#xff09;5 知道李白吗&#xff1f;6 那你会写诗吗&#xff1f;6 那你讲脑经急转弯吗&#xf…

linux服务器安装docker(学习中)

linux服务器安装docker1、docker官网寻找官方文档1.1、卸载之前的docker1.2、安装yum工具类1.3、配置docker下载源的地址1.4、安装最新稳定版的docker1.5、启动docker1.6、docker镜像下载加速2、docker-卷-映射和挂载2.1、nginx1、docker官网寻找官方文档 然后根据官网文档进行…

高性能零售IT系统的建设08-9年来在互联网零售O2O行业抗黑产、薅羊毛实战记录及打法

前言 2012年左右转入互联网应用&#xff0c;由于本身在学校时就涉足过远程医疗影像中的DICOM安全领域这块&#xff0c;因此也是机缘巧合我进入互联网第一年就遇上了一次亿级的DDOS攻击以及千万级CC攻击短信系统的对抗。那时在公司一战成名&#xff0c;直接从team leader升到了主…

adb remount原理

1, 输入"fastboot flashing unlock" in adb ,waiting for the device 2, 输入"fastboot flashing unlock_critical"in adb ,waiting for the device 3, 输入"fastboot reboot" reboot the stb, press any key entering the boot mode 4, after…

tensorflow入门(三)tensorflow下神经网络参数的设置

参考 Tensorflow入门 - 云社区 - 腾讯云 神经网络中的参数是神经网络实现分类或回归问题中重要的部分。在tensorflow中&#xff0c;变量(tf.Variable)的作用就是保存和更新神经网络中的参数的。在tensorflow中&#xff0c;变量(tf.Variable)的作用就是保存和更新神经网络的参…

Educational Codeforces Round 123 (Rated for Div. 2) D. Cross Coloring

Problem - D - Codeforces 翻译&#xff1a; 有一张纸&#xff0c;可以用大小为&#x1d45b;&#x1d45a;:&#x1d45b;行和&#x1d45a;列的单元格表示。所有的细胞最初都是白色的。 &#x1d45e;操作已应用到工作表。他们的&#x1d456;-th可以描述如下: &#x1d4…

前端工程化项目的思考

这是一篇个人使用前端工程开发项目的思考&#xff0c;希望可以帮助到你。完全是一篇综合概念应该是很多东西&#xff0c;我也不清楚会有多少字&#xff0c;估计会对刚刚开始的人看起来比较迷&#xff0c;但也是没有办法的事情 1.前端脚本语言开发的作者我想应该也想不到js会发展…

Spark

1 Spark作业提交流程 2 Spark提交作业参数 1&#xff09;在提交任务时的几个重要参数 executor-cores —— 每个executor使用的内核数&#xff0c;默认为1&#xff0c;官方建议2-5个 num-executors —— 启动executors的数量&#xff0c;默认为2 executor-memory —— executor…

【OpenCV学习】第9课:形态学操作的应用-提取水平线丶垂直线

仅自学做笔记用,后续有错误会更改 理论 图像在进行形态学操作的时候&#xff0c; 可以通过自定义的结构元素实现结构元素对输入图像的一些对象敏感丶对另外一些对象不敏感&#xff0c; 这样就会让敏感的对象改变而不敏感的对象保留输出。 通过使用两个最基本的形态学操作 - 膨…

华为云服务-运维篇-弹性负载均衡

文章目录一、什么是负载均衡二、我们为什么需要负载均衡1、生活中需要它的类似场景2、生活场景中协调者&#xff08;负载均衡)作用3、协调者&#xff08;负载均衡)引入后的变化三、华为云平台-如何做负载均衡弹性负载均衡-ELB四、总结一、什么是负载均衡 负载均衡构建在原有网…

【数据挖掘】薪酬分段对应工作经验/学历画柱状图【招聘网站的职位招聘数据预处理】

文章目录一.需求背景1.1 需求分析二.数据处理(对给定职位&#xff0c;汇总薪酬分段对应工作经验要求数据&#xff0c;画柱状图&#xff1b;)2.1 事前准备2,1 处理开始三.数据处理(对给定职位&#xff0c;汇总薪酬分段对应学历要求数据&#xff0c;画柱状图&#xff1b;)四.附源…

吉林大学 超星慕课 高级语言程序设计 实验08 结构化程序设计(2022级)

本人能力有限&#xff0c;发出只为帮助有需要的人。 建议同学们自己写完后再进行讨论。 其中的代码均没能在oj上进行测试&#xff0c;因此可能有误&#xff0c;请谅解。 除此以外部分题目设计深度优先搜索&#xff0c;因此可以分别用递归和堆栈实现&#xff0c;堆栈方法为了…

JavaScript进阶教程——异步编程、封装Ajax

异步编程 什么是同步与异步&#xff1a; 同步&#xff1a;一件事没做完&#xff0c;只能等待&#xff0c;完成之后再去做另一件事 异步&#xff1a; 两件事可以同时进行 前端开发中最常见的两种异步情况&#xff1a; ajax: 向后台请求数据计时器&#xff1a; setInterval se…

Python学习基础笔记四十一——sys模块

sys模块是与Python解释器交互的一个接口。 sys.argv 命令行参数List&#xff0c;第一个元素是程序本身路径 sys.exit(n) 退出程序&#xff0c;正常退出时exit(0),错误退出sys.exit(1) sys.version 获取Python解释程序的版本信息 sys.path 返…

ARM Cortex M3处理器概述

Cortex-M3概述 2004年ARM发布作为新型Corex处理器内核系列首款的Cortex-M3处理器。 STM32系列基于专为高性能、低成本、低功耗的嵌入式应用专门设计的ARM Cortex-M内核。 STM32命名规则 STMF103xx系统结构 1.使用高性能的ARM Cortex-M3 32位RISC内核 2.工作频率为72MHZ 3.内…

shell脚本监控文件夹文件实现自动上传数据到hive表

sh createtb.sh “tablename;field1,field2,field3,field4,field5,field6,field7;partition1,partition2” 数据库名&#xff1a;observation &#xff08;脚本里写死了&#xff09; 表名&#xff1a;tablename 指定名&#xff1a;field1,field2,field3,field4,field5,field6,f…

分别使用Alpine、Docker制作jdk镜像

目录 制作 jdk 1.0 镜像 ——Docker 1.创建文件夹上传jdk的安装包,和在同级目录下编写Dockerfile文件 2.编写 Dockerfile 文件 3.执行Dockerfile文件&#xff0c;初次依赖镜像的时候会下载相应镜像 优化制作jdk镜像&#xff08;缩小内存大小&#xff09;——使用alpine …

【致敬世界杯】球迷(我)和足球的故事

目录 一、第一次接触足球 二、回味无穷的2018世界杯 三、致敬世界杯 3.1 源代码 3.2 思路 3.3 关于图片 一、第一次接触足球 踢足球是一项优秀的运动&#xff0c;它可以锻炼身体&#xff0c;增强团队合作精神&#xff0c;并为人们带来快乐和满足感。回忆起小学时候第一次…

OpenCV和RTSP的综合研究

一、RTSP是什么&#xff1f;用来干什么&#xff1f; RTSP&#xff08;Real Time Streaming Protocol&#xff09;&#xff0c;RFC2326&#xff0c;实时流传输协议&#xff0c;是TCP/IP协议体系中的一个应用层协议&#xff0c;由哥伦比亚大学、网景和RealNetworks公司提交的IET…