Java Day13 多线程

多线程

  • 1、 方式一 Thread
  • 2、实现Runnable接口
  • 3、实现 Callable接口
  • 4、与线程有关的操作方法
  • 5、线程安全问题
    • 5.1 取钱案例
    • 5.2 线程同步
      • 5.2.1 同步代码块
      • 5.2.2 同步方法
      • 5.2.3 Lock锁
  • 6、线程池
    • 6.2 创建线程池
      • 6.2.1 使用ExecutorService创建
      • 新任务策略
      • 6.2.2 使用Executors工具类创建
      • 核心线程数量问题
  • 7、并发、并行,线程生命周期
  • 7、乐观锁、悲观锁

1、 方式一 Thread

继承 Thread类
优点:编码简单
缺点:java是单继承,继承了一个类,就不能继承其他,可拓展性不强。

注意:
①子线程一定要调用start方法,而不是run方法,调用run方法,还是相当于在main线程中创建了一个实例对象,在运行她的方法, 而已,是单线程的。调用strat方法,虽然底层还是调用run方法,但是他会告诉cpu说我们另外启动了一个线程。
②子线程启动一定要在主线程任务之前。
③每次执行的结果会不同。

package com.cky.file;

public class MyThread extends  Thread{
    @Override
    public void run() {
        for (int i = 0; i <5 ; i++) {
            System.out.println("子线程"+i);
        }
    }
}

package com.cky.file;


public class Endecode {
    public static void main(String[] args) throws Exception {

        Thread thread=new MyThread();
        thread.start();

        for (int i = 0; i <5 ; i++) {
            System.out.println("主线程"+i);

        }

    }}



2、实现Runnable接口

在这里插入图片描述

package com.cky.file;

public class MyRunnable implements  Runnable{
    @Override
    public void run() {
        for (int i = 0; i <5 ; i++) {
            System.out.println("子线程"+i);
        }
    }
}

package com.cky.file;


public class Endecode {
    public static void main(String[] args) throws Exception {

        //创建一个任务对象
        MyRunnable myRunnable=new MyRunnable();
        //调用线程类的start方法
        new Thread(myRunnable).start();

        //匿名内部类
        Runnable runnable = new Runnable() {

            @Override
            public void run() {
                for (int i = 0; i < 5; i++) {
                    System.out.println("子线程" + i);
                }
            }
        };
        new Thread(runnable).start();

        //简化形式1

        new Thread(new Runnable() {

            @Override
            public void run() {
                for (int i = 0; i < 5; i++) {
                    System.out.println("子线程" + i);
                }
            }
        }).start();

        //形式2
        new Thread(()-> {
                for (int i = 0; i < 5; i++) {
                    System.out.println("子线程" + i);

            }
        }).start();
        for (int i = 0; i <5 ; i++) {
            System.out.println("主线程"+i);

        }

    }}



由于Runnable是一个函数式接口,匿名内部类可以使用lambda形式
在这里插入图片描述

3、实现 Callable接口

上边两种实现方法,都不能获得线程执行的结果并返回,方法3可以。
实现Callable接口 将该接口 封装为一个FutureTask 任务对象,最后在交给线程。
是一个泛型接口 这里String 是要返回的类型 可以定义为其他类型

package com.cky.file;

import java.util.concurrent.Callable;

public class MyCallable implements Callable<String> {
    private int n;

    public MyCallable(int n) {
        this.n = n;
    }

    @Override
    public String call() throws Exception {
        int sum=0;
        for (int i = 1; i <= n; i++) {
            sum+=i;
        }
        return "1-"+n+"的和为:"+sum;
    }
}

package com.cky.file;


import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class Endecode {
    public static void main(String[] args) throws Exception {

   //创建一个Callable对象
        Callable<String> callable=new MyCallable(100);

        //封装为一个FutureTask对象
        //FutureTask 是一个任务对象 实现了Runnable接口
        // 在执行完毕后 可以通过get方法获得执行结果
        FutureTask<String> f1 = new FutureTask<>(callable);
        //交给一个Thread对象
        new Thread(f1).start();
        //获得执行结果 注意:执行未结束 是不会取得结果的
        String s = f1.get();
        System.out.println(s);//1-100的和为:5050
    }}



在这里插入图片描述

4、与线程有关的操作方法

在这里插入图片描述

package com.cky.file;

public class MyThread extends  Thread{
    public MyThread( String name){
        super(name);
    }
    @Override
    public void run() {

        Thread t=Thread.currentThread();
        for (int i = 0; i <3 ; i++) {
            System.out.println(t.getName()+i);
        }
    }
}

package com.cky.file;


import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class Endecode {
    public static void main(String[] args) throws Exception {
        MyThread myThread1=new MyThread("子线程1");
        //修改名字
//        myThread1.setName("子线程1");
        myThread1.start();
        MyThread myThread2=new MyThread("子线程2");
//        myThread2.setName("子线程2");
        myThread2.start();
        //哪个线程在执行 就是哪个线程
        Thread m = Thread.currentThread();
        String name = m.getName();
        System.out.println(name);
        for (int i = 0; i < 3; i++) {
            System.out.println(name+"线程"+i);

        }
    }}



package com.cky.file;


import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class Endecode {
    public static void main(String[] args) throws Exception {
//当输出为3时 ,延缓5秒在运行
        for (int i = 0; i < 5; i++) {
            System.out.println(i);
            if(i==3)
                Thread.sleep(5000);

        }
    }}



package com.cky.file;


import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class Endecode {
    public static void main(String[] args) throws Exception {
        MyThread myThread1=new MyThread("子线程1");
        myThread1.start();
        myThread1.join();//当前线程执行完毕 再往下进行
        MyThread myThread2=new MyThread("子线程2");
        myThread2.start();
        myThread2.join();
        Thread m = Thread.currentThread();
        String name = m.getName();
        System.out.println(name);
        for (int i = 0; i < 3; i++) {
            System.out.println(name+"线程"+i);

        }
    }}



子线程10
子线程11
子线程12
子线程20
子线程21
子线程22
main
main线程0
main线程1
main线程2

5、线程安全问题

5.1 取钱案例

造成线程安全问题原因:
同时存在多个线程,访问同一个共享资源,且都要修改该共享资源。
同时进入钱够的判断,造成线程 不安全

package com.cky.file;

public class Account {
    private double money;

    public Account(double money) {
        this.money = money;
    }

    public double getMoney() {
        return money;
    }

    public void setMoney(double money) {
        this.money = money;
    }
    public void drawmoney(double money){
        String name=Thread.currentThread().getName();
        if (this.money>=money){
            System.out.println(name+"来取钱成功");
            this.money-=money;
            System.out.println(name+"取后,剩余:"+this.money);

        }
        else{
            System.out.println("钱不够");
        }

    }
}

package com.cky.file;

public class MyThread extends  Thread{
    private Account account;
    public MyThread( Account account,String name){
        super(name);
        this.account=account;
    }
    @Override
    public void run() {
//取钱
        account.drawmoney(10000);
        }
    }

package com.cky.file;


import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class Endecode {
    public static void main(String[] args) {
        //创建一个共享账户
        Account account=new Account(10000);
        //两个线程
        MyThread myThread1=new MyThread(account,"小红");
        MyThread myThread2=new MyThread(account,"小明");
        myThread1.start();
        myThread2.start();



    }}



小红来取钱成功
小明来取钱成功
小红取后,剩余:0.0
小明取后,剩余:-10000.0

5.2 线程同步

解决线程安全问题的办法
在这里插入图片描述
在这里插入图片描述

5.2.1 同步代码块

  public void drawmoney(double money){
        String name=Thread.currentThread().getName();
        synchronized (this) {
            if (this.money>=money){
                System.out.println(name+"来取钱成功");
                this.money-=money;
                System.out.println(name+"取后,剩余:"+this.money);

            }
            else{
                System.out.println("钱不够");
            }
        }

实例方法 同步代码块 通常加this 代表当前资源 比如小红和小黑共享一个账户 小白和小亮共享一个账户,使用this 保证了 只锁住同一个账户 不被多个访问 但不会去锁住别人的账户。
如果是静态方法 每个类只有一份 只允许一个访问 通常用 类.class 来锁住所有,只允许一个人访问。

   public static void text(){
        synchronized (Account.class){}
    }

在这里插入图片描述

5.2.2 同步方法

 public synchronized void drawmoney(double money){
        String name=Thread.currentThread().getName();
       
            if (this.money>=money) {
                System.out.println(name + "来取钱成功");
                this.money -= money;
                System.out.println(name + "取后,剩余:" + this.money);

            }
            else{
                System.out.println("钱不够");
            }
        
    }

在这里插入图片描述

5.2.3 Lock锁

在这里插入图片描述

package com.cky.file;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Account {
    private double money;
    //为每个账户创建一个锁对象
    private final Lock lock=new ReentrantLock();
    public Account(double money) {
        this.money = money;
    }

    public double getMoney() {
        return money;
    }
    public void setMoney(double money) {
        this.money = money;
    }
    public void drawmoney(double money){
        String name=Thread.currentThread().getName();
        //加锁
       lock.lock();
        try {
            if (this.money>=money) {
                System.out.println(name + "来取钱成功");
                this.money -= money;
                System.out.println(name + "取后,剩余:" + this.money);

            }
            else{
                System.out.println("钱不够");
            }
           
        } catch (Exception e) {
            e.printStackTrace();
        }
        finally {
            //写在finally里,为了防止上边代码出错,导致没有解锁
            lock.unlock();
        }

    }
}

6、线程池

如果每一个线程都需要我们去新创建一个线程的话,就会耗资源并且耗时(创建线程是一件耗时的事)
此时就需要线程池,线程池可以复用线程,不用经常去创建线程。
并且线程池也可以确定任务队列中任务的个数。防止任务过多,导致内存溢出。
在这里插入图片描述

6.2 创建线程池

在这里插入图片描述

6.2.1 使用ExecutorService创建

在这里插入图片描述
在这里插入图片描述

package com.cky.file;

public class MyRunnable implements  Runnable{
    private int n;

    public MyRunnable(int n) {
        this.n = n;
    }

    @Override
    public void run()  {
        int sum=0;
        for (int i = 1; i <= n; i++) {
            sum+=i;
        }

        System.out.println(Thread.currentThread().getName()+"-1-"+n+"的和为:"+sum);
        try {
            Thread.sleep(100000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
package com.cky.file;


import java.util.concurrent.*;

public class Endecode {
    public static void main(String[] args) {
       //创建一个线程池对象
       /* int corePoolSize,
        int maximumPoolSize,
        long keepAliveTime,
        TimeUnit unit,
        BlockingQueue<Runnable> workQueue,
        ThreadFactory threadFactory,
        RejectedExecutionHandler handler*/
        ExecutorService pool=new ThreadPoolExecutor(3,5,8,TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(4),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
        MyRunnable myRunnable=new MyRunnable(100);
        pool.execute(myRunnable);
        pool.execute(myRunnable);
        pool.execute(myRunnable);
        //开始往任务队列加
        pool.execute(myRunnable);
        pool.execute(myRunnable);
        pool.execute(myRunnable);
        pool.execute(myRunnable);
        //开始使用临时线程
        pool.execute(myRunnable);
        pool.execute(myRunnable);
//        开始使用任务队列满的措施
        pool.execute(myRunnable);


    }}
D:\JAVA\jdk-17.0.8\bin\java.exe "-javaagent:D:\SOftware\idea\IntelliJ IDEA 2021.3.2\lib\idea_rt.jar=49289:D:\SOftware\idea\IntelliJ IDEA 2021.3.2\bin" -Dfile.encoding=UTF-8 -classpath E:\java_code\project\out\production\hello-app;E:\java_code\project\hello-app\lib\dom4j-2.1.4.jar;E:\java_code\project\hello-app\lib\commons-io-2.15.1.jar com.cky.file.Endecode
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@5b480cf9[Not completed, task = java.util.concurrent.Executors$RunnableAdapter@5f184fc6[Wrapped task = com.cky.file.MyRunnable@3feba861]] rejected from java.util.concurrent.ThreadPoolExecutor@6f496d9f[Running, pool size = 5, active threads = 5, queued tasks = 4, completed tasks = 0]
	at java.base/java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2065)
	at java.base/java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:833)
	at java.base/java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1365)
	at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:123)
	at com.cky.file.Endecode.main(Endecode.java:31)
pool-1-thread-1-1-100的和为:5050
pool-1-thread-2-1-100的和为:5050
pool-1-thread-5-1-100的和为:5050
pool-1-thread-3-1-100的和为:5050
pool-1-thread-4-1-100的和为:5050

//我们可以看到 使用了临时线程 并且使用了任务满时的策略。

新任务策略

在这里插入图片描述

package com.cky.file;

import java.util.concurrent.Callable;

public class MyCallable implements Callable<String> {
    private int n;

    public MyCallable(int n) {
        this.n = n;
    }

    @Override
    public String call() throws Exception {
        int sum=0;
        for (int i = 1; i <= n; i++) {
            sum+=i;
        }
        return Thread.currentThread().getName()+"-1-"+n+"的和为:"+sum;
    }
}

package com.cky.file;


import java.util.concurrent.*;

public class Endecode {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
       //创建一个线程池对象
       /* int corePoolSize,
        int maximumPoolSize,
        long keepAliveTime,
        TimeUnit unit,
        BlockingQueue<Runnable> workQueue,
        ThreadFactory threadFactory,
        RejectedExecutionHandler handler*/
        ExecutorService pool=new ThreadPoolExecutor(3,5,8,TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(4),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
        Future<String> f1 = pool.submit(new MyCallable(100));
        Future<String> f2 = pool.submit(new MyCallable(200));
        Future<String> f3 =pool.submit(new MyCallable(300));
        Future<String> f4 =pool.submit(new MyCallable(400));
        System.out.println(f1.get());
        System.out.println(f2.get());
        System.out.println(f3.get());
        System.out.println(f4.get());

    }}



pool-1-thread-1-1-100的和为:5050
pool-1-thread-2-1-200的和为:20100
pool-1-thread-3-1-300的和为:45150
pool-1-thread-3-1-400的和为:80200

6.2.2 使用Executors工具类创建

在这里插入图片描述
在这里插入图片描述
创建单个线程池

   ExecutorService pool = Executors.newSingleThreadExecutor();
        pool.execute(new MyRunnable(100));

使用该种方式的风险
在这里插入图片描述

核心线程数量问题

如果时IO密集型 则一般配置 cpu数量*2
如果是计算密集型 则一般是 cpu数量+1

7、并发、并行,线程生命周期

在这里插入图片描述

并发
在这里插入图片描述
并行
在这里插入图片描述
在这里插入图片描述
生命周期
在这里插入图片描述
在这里插入图片描述

7、乐观锁、悲观锁

悲观锁:一上来就加锁,线程安全,性能较差。
乐观锁:不加锁,等到要开始出现线程安全问题时,才开始控制。

package com.cky.file;

import java.util.concurrent.atomic.AtomicInteger;

public class MyRunnable implements  Runnable{

   private AtomicInteger count=new AtomicInteger();
    @Override
    public void run()  {
        for (int i = 0; i <100 ; i++) {
            System.out.println(Thread.currentThread().getName()+"count===>"+count.incrementAndGet());
        }
    }
}

package com.cky.file;


import java.util.concurrent.*;

public class Endecode {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
      //乐观锁
        //100个线程 对一个变量 各加100次
        MyRunnable myRunnable=new MyRunnable();
        for (int i = 0; i <100 ; i++) {
            new Thread(myRunnable).start();
        }

    }}



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

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

相关文章

2024年云仓酒庄佛山发布会:赋能

原标题&#xff1a;2024年云仓酒庄佛山发布会圆满落幕&#xff0c;朱囿臻总赋能引领行业新篇章 近日&#xff0c;备受瞩目的云仓酒庄佛山发布会圆满落幕。此次发布会汇聚了业内精英、经销商代表以及媒体人士&#xff0c;共同见证了云仓酒庄在佛山市场的启航。在此&#xff0c;…

智慧公厕:卫生、便捷、安全的新时代厕所变革

在城市快速发展的背景下&#xff0c;公共厕所的建设和管理变得越来越重要。智慧公厕作为厕所变革的一项全新举措&#xff0c;通过建立公共厕所全面感知监测系统&#xff0c;以物联网、互联网、大数据、云计算、自动化控制技术为支撑&#xff0c;实现对公共厕所的智能化管理和运…

练习4-权重衰减(李沐函数简要解析)

环境:练习1的环境 代码详解 0.导入库 import torch from torch import nn from d2l import torch as d2l1.初始化数据 这里初始化出train_iter test_iter 可以查一下之前的获取Fashion数据集后的数据格式与此对应 n_train, n_test, num_inputs, batch_size 20, 100, 200, …

50. 【Linux教程】源码安装软件

本小节介绍如何使用软件的源码包安装软件&#xff0c;以安装 nginx 源码包为例。 1.下载软件源码包 使用如下命令下载 nginx 源码包&#xff1a; wget http://nginx.org/download/nginx-1.18.0.tar.gz执行结果如下图所示&#xff1a; 2.解压源码包 下载好了压缩包之后&#…

基于Java+SpringBoot+vue+element实现前后端分离玩具商城系统

基于JavaSpringBootvueelement实现前后端分离玩具商城系统 博主介绍&#xff1a;多年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 央顺技术团队 Java毕设项目精品实战案例《1000套》 欢迎点赞 收藏 ⭐留言 文…

Linux网络编程: TCP协议之序号和确认号详解

一、TCP协议首部 二、序号&#xff08;Sequence Number&#xff09; 32位&#xff0c;表示该报文段所发送数据的第一个字节的编号。 实际上 TCP 的序号并不是按照 “一条两条” 这样的方式来编号的。在TCP连接中所传输字节流的每一个字节都会按顺序编号&#xff0c;由于序列号…

CTF-reverse-每日练题-xxxorrr

题目链接 https://adworld.xctf.org.cn/challenges/list 题目详情 xxxorrr ​ 解题报告 下载得到的文件使用ida64分析&#xff0c;如果报错就换ida32&#xff0c;得到分析结果&#xff0c;有main函数就先看main main函数分析 v6 main函数中&#xff0c;v6的值是__readfsqwor…

Java基础学习笔记三

环境变量CLASSPATH classpath环境变量是隶属于java语言的&#xff0c;不是windows操作系统的&#xff0c;和PATH环境变量完全不同classpath环境变量是给classloader&#xff08;类加载器&#xff09;指路的java A 。执行后&#xff0c;先启动JVM&#xff0c; JVM启动classload…

聚类算法( clustering algorithm):

在前两章&#xff0c;我们学的是&#xff1a;线性回归&#xff0c;逻辑回归&#xff0c;深度学习(神经网络)&#xff0c;决策树&#xff0c;随即森林算法。他们都是监督学习的例子。 在这一章里&#xff0c;我们将学习非监督学习的算法。 什么是聚类算法&#xff1a; 聚类算…

C语言结构体详解

1、结构体的声明 结构体是一些值的集合&#xff0c;这些值被称为成员变量。结构体中的每个成员可以是不同类型的变量。 语法&#xff1a; struct tag //关键词 标签 { member- list ;//成员清单 }variable- list ;//变量清单 通常结构体描述的是一个复杂对象&#xff0c;比…

【Linux】多线程概念 | POSIX线程库

文章目录 一、线程的概念1. 什么是线程Linux下并不存在真正的多线程&#xff0c;而是用进程模拟的&#xff01;Linux没有真正意义上的线程相关的系统调用&#xff01;原生线程库pthread 2. 线程和进程的联系和区别3. 线程的优点4. 线程的缺点5. 线程异常6. 线程用途 二、二级页…

2023 re:Invent | Amazon Q 与 Amazon CodeWhisperer 面向企业开发者提效利器

2023 年&#xff0c;以 GPT 为代表的生成式 AI 引爆了新一轮技术热潮&#xff0c;短短一年的时间内&#xff0c;生成式 AI 已经成为科技世界发展的核心。作为云计算的行业风向标盛会 re &#xff0c;本届: Invent 全球大会紧跟生成式 AI 浪潮&#xff0c;推出名为“ Amazon Q ”…

【方法】想要修改PDF文件怎么办?

在工作上&#xff0c;我们经常需要用到PDF文件&#xff0c;但需要修改PDF时&#xff0c;有些小伙伴却不知道怎么办&#xff0c;那就一起来看看以下两个方法吧&#xff01; 方法一&#xff1a;使用PDF编辑器 PDF文件可以通过PDF阅读器或者浏览器在线打开&#xff0c;但无法进行…

【DFS】第十三届蓝桥杯省赛C++ B组《扫雷》(C++)

【题目描述】 小明最近迷上了一款名为《扫雷》的游戏。 其中有一个关卡的任务如下&#xff1a; 在一个二维平面上放置着 n 个炸雷&#xff0c;第 i 个炸雷 (xi,yi,ri) 表示在坐标 (xi,yi) 处存在一个炸雷&#xff0c;它的爆炸范围是以半径为 ri 的一个圆。 为了顺利通过这片…

GIS设计与开发的学习笔记

目录 一、简答题 1.GeoDatabase数据模型结构类型与四种关系。 2.组件式GIS的基本思想是什么&#xff1f; 3.请简述创建空间书签的实现逻辑。 4.请问与地理要素编辑相关的类有哪些&#xff1f;&#xff08;列举至少五个类&#xff09; 5.利用ArcGIS Engine提供的栅格运算工…

电玩体验店怎么计时,佳易王ps5计时计费管理控制系统操作教程

电玩体验店怎么计时&#xff0c;佳易王ps5计时计费管理控制系统操作教程 一、前言 以下软件操作教程以 佳易王电玩计时计费管理系统软件V17.9为例说明 件文件下载可以点击最下方官网卡片——软件下载——试用版软件下载 1、电玩体验馆管理软件在计时的同时可以设置定时提醒&…

大模型第一讲笔记

目录 1、人工智能基础概念全景介绍... 2 1.1 人工智能全景图... 2 1.2 人工智能历史... 2 1.3 人工智能——机器学习... 3 监督学习、非监督学习、强化学习、机器学习之间的关系... 3 监督学习... 4 无监督学习... 5 强化学习... 5 深度学习... 6 2、语言模型的发展及…

视频素材库app推荐的地方在哪里找?

视频素材库app推荐的地方在哪里&#xff1f;这是很多短视频创作者都会遇到的问题。别着急&#xff0c;今天我就来给大家介绍几个视频素材库app推荐的网站&#xff0c;让你的视频创作更加轻松有趣&#xff01; 蛙学网&#xff1a;视频素材库app推荐的首选当然是蛙学网啦&#xf…

OKR如何与组织的整体战略和计划相结合?

OKR&#xff08;Objectives and Key Results&#xff0c;目标与关键成果&#xff09;作为一种流行的目标管理方法&#xff0c;正逐渐成为组织实现战略目标的重要手段。本文将探讨OKR如何与组织的整体战略和计划相结合&#xff0c;从而推动组织的持续发展。 首先&#xff0c;我…

dlib:人脸识别的魔法工具箱

引言 在数字化的世界中&#xff0c;人脸识别技术已经不再是科幻小说的专利&#xff0c;而是我们日常生活中的一部分。从解锁手机到机场安检&#xff0c;人脸识别技术正在逐步改变我们与世界的互动方式。而在这个领域中&#xff0c;有一个名为dlib的英雄&#xff0c;以其强大的功…