多线程详解(理论与实践的最佳融合方案)

news2024/9/20 1:12:41

在这里插入图片描述

一.概述

线程简介

总而言之,就是在同一时间,做了不同的事情,正所谓一石二鸟,一箭双雕,赔了夫人又折兵

生活中很多事情都可以看作是多线程的例子。比如:

  1. 煮饭:煮饭需要同时加热米饭和煮菜,如果只有一个火力,就只能按照一定的顺序依次进行,而使用多个火力则可以同时加热多个部分,从而节省时间。
  2. 洗衣服:洗衣服需要同时洗涤多个衣物,如果只有一个洗衣机,就只能按照一定的顺序依次进行,而使用多个洗衣机则可以同时洗涤多个衣物,从而节省时间。
  3. 开车:开车需要同时控制加速、刹车、方向盘等多个部分,如果只有一个人,就只能按照一定的顺序依次进行,而使用多个人则可以同时控制多个部分,从而提高驾驶的安全性和效率。

然后,我们使用官方点的语言解释一下什么是多线程:

多线程是一种计算机程序设计技术,它可以同时执行多个线程(或者说子任务),以实现更高效的计算机程序。在单线程的计算机程序中,程序只能按照一定的顺序执行,而在多线程的程序中,多个线程可以在同一时间内并发执行,从而提高程序的并发性和执行效率。

举个例子,如果你正在使用电脑进行网页浏览,当你点击某个链接时,网页就会从服务器端获取相应的内容并在你的电脑上显示出来。如果网页的内容非常多,而你的电脑只有一个处理器,那么你就只能等待这些内容全部加载完成后才能浏览网页。但如果你使用的是多线程的浏览器,就可以同时加载多个网页的内容,从而提高浏览网页的速度。

多线程技术在现代计算机系统中非常重要,广泛应用于各种计算机程序中,如Web服务器、操作系统、数据库等等。它可以帮助计算机程序更好地利用多核CPU,提高程序的响应速度和并发能力。

总的来说,多线程技术可以帮助我们更好地利用多核CPU,提高程序的响应速度和并发能力,从而提高我们的生活效率。

普通方法调用和多线程对比

image-20230523233120949

线程和进程的联系与区别

进程和线程都是操作系统中的基本概念,但它们有很不同的特点和用途。

相同点:

  1. 进程和线程都可以作为程序的执行单位;
  2. 一个进程可以包含多个线程;
  3. 进程和线程都具有并发性。

不同点:

  1. 进程是操作系统资源分配的基本单位,而线程是CPU调度的基本单位。进程拥有自己独立的地址空间、系统资源(如文件句柄、打开网络连接等)和内核对象(如进程句柄、信号处理函数等),一个进程无法访问另一个进程的资源,而线程则共享所属进程的资源,如打开的文件、上下文环境等;
  2. 创建或销毁进程时,操作系统需要完成大量的工作,比如分配和初始化各种数据结构、加载程序代码和数据等,这将花费较大的时间和系统开销。而创建或销毁线程时,操作系统只需实现简单的数据结构即可,工作量远远小于进程,因此更加轻量级;
  3. 进程之间不能直接通信,必须通过一些机制(如管道、消息队列、共享内存等)来进行通信,而线程间可以直接共享相同的内存空间,并通过读写同一变量等方式来进行通信,这种通信的效率更高;
  4. 进程启动的时间长,切换上下文所需的时间较多,线程则启动快、上下文切换快。

总结来说,进程和线程是操作系统中不同层级的并发调度单位,进程之间相互隔离,线程之间共享资源,根据具体应用场景选择合适的方案可提高程序性能。

线程实现

Java多线程有以下五种实现方法:

  1. 继承Thread类

继承Thread类是实现多线程最常用的方法。通过继承Thread类,可以重写run()方法,在run()方法中编写多线程的逻辑代码。如下所示:

public class MyThread extends Thread {
   public void run() {
       // 多线程的逻辑代码
   }
}
  1. 实现Runnable接口

实现Runnable接口也是实现多线程的一种方式。通过实现Runnable接口,可以创建多个实例对象,然后将它们作为参数传递给Thread类的构造方法中。如下所示:

public class MyRunnable implements Runnable {
   public void run() {
       // 多线程的逻辑代码
   }
}

public class MyThread extends Thread {
   private MyRunnable mRunnable;
   
   public void setRunnable(MyRunnable mRunnable) {
       this.mRunnable = mRunnable;
   }
   
   @Override
   public void run() {
       mRunnable.run();
   }
}
  1. 实现Callable接口

实现Callable接口与实现Runnable接口类似,不同之处在于Callable接口可以返回一个结果。Callable接口可以用来执行一些需要返回结果的任务。如下所示:

public class MyCallable implements Callable<Integer> {
   public Integer call() throws Exception {
       // 多线程的逻辑代码,返回一个整数结果
       return result;
   }
}

public class MyThread extends Thread {
   private MyCallable mCallable;
   
   public void setCallable(MyCallable mCallable) {
       this.mCallable = mCallable;
   }
   
   @Override
   public Integer call() throws Exception {
       return mCallable.call();
   }
}
  1. 实现Future接口

Future接口可以用来获取异步计算的结果。通过实现Future接口,可以在调用异步方法时获取到异步计算的结果。如下所示:

public class MyFuture implements Future<Integer> {
   private Integer result;
   
   public void setResult(Integer result) {
       this.result = result;
   }
   
   @Override
   public Integer get() throws Exception {
       // 在获取结果前可以进行一些操作,如等待一段时间等操作。这里直接返回计算结果。如果计算还没有完成,则会一直阻塞等待。如果计算已经完成,则会立即返回计算结果。如果出现异常,则会抛出异常。如下所示:
       while (!isDone()) {} 
       // 这里是一个无限循环,直到isDone()返回true才跳出循环。isDone()表示异步计算是否已经完成。如果isDone()返回true,则说明异步计算已经完成。否则,在下一次循环中继续等待。这里使用了一个while循环来等待计算结果的返回。这种方式适用于不需要知道异步计算何时完成的情况。如果需要知道异步计算何时完成,可以使用FutureTask类来进行异步计算并获取异步计算的结果。如下所示:
       

在这里,我们再稍微多讲一点

Java多线程五种实现方法可以混合使用。

在实际开发中,我们可以根据需要选择不同的多线程实现方式。例如,如果我们需要执行一个异步计算的任务,可以使用Callable接口和Future接口来实现;如果我们需要执行一个有返回值的任务,可以使用Runnable接口和Future接口来实现;如果我们需要执行一个线程池中的任务,可以使用Thread类或Runnable接口来实现。

同时,我们也可以将多种多线程实现方式进行组合使用。例如,我们可以将Runnable接口和Callable接口进行组合使用,来实现一个既可以执行任务又可以返回结果的多线程程序。如下所示:

public class MyRunnable implements Runnable, Callable<Integer> {
   private Integer result;
   
   public void run() {
       // 多线程的逻辑代码
       result = 123; // 这里可以设置异步计算的结果
   }
   
   @Override
   public Integer call() throws Exception {
       // 在获取结果前可以进行一些操作,如等待一段时间等操作。这里直接返回计算结果。如果计算还没有完成,则会一直阻塞等待。如果计算已经完成,则会立即返回计算结果。如果出现异常,则会抛出异常。如下所示:
       while (!isDone()) {} // 这里是一个无限循环,直到isDone()返回true才跳出循环。isDone()表示异步计算是否已经完成。如果isDone()返回true,则说明异步计算已经完成。否则,在下一次循环中继续等待。这里使用了一个while循环来等待计算结果的返回。这种方式适用于不需要知道异步计算何时完成的情况。如果需要知道异步计算何时完成,可以使用FutureTask类来进行异步计算并获取异步计算的结果。如下所示:
       while (!isDone()) {} // 这里是一个无限循环,直到isDone()返回true才跳出循环。isDone()表示异步计算是否已经完成。如果isDone()返回true,则说明异数

线程状态

常见的线程状态包括:

  1. 新建(New):当一个线程被创建但还没有开始运行时,它的状态为新建状态。
  2. 就绪(Runnable):当一个线程已经创建,且它的所有前置条件都已完成,它等待CPU时间片分配以执行任务时,它的状态为就绪状态。
  3. 运行(Running):当一个线程得到了处理器分配时间并正在执行任务时,它的状态为运行状态。
  4. 阻塞(Blocked):当一个线程由于某些原因无法获得所需资源而阻塞时,它的状态为阻塞状态。这可能是因为它正在等待磁盘或网络I / O操作、等待锁或等待其他线程完成等。
  5. 等待(Waiting):当一个线程通过调用wait()方法进入无限期等待状态时,它的状态为等待状态,这通常是在等待另一个线程通知所等待的条件已被满足。
  6. 超时等待(Timed Waiting):当一个线程通过调用带有超时参数的sleep()、wait()或join()方法等待一段时间后进入此状态,它的状态为超时等待状态。
  7. 终止(Terminated):当一个线程已经完成它的任务并退出运行时,或者因未处理异常而突然退出运行时,它的状态为终止状态。

这些不同的线程状态可以在多线程应用程序中解决并发问题。了解线程的状态和各种状态之间的转换是编写高效且正确的多线程代码的关键。

现场同步

线程现场同步是指在多线程编程中,为了保证多个线程访问共享资源时的正确性,需要对这些资源进行加锁和解锁的操作。

当多个线程同时访问同一个共享资源时,如果没有进行同步处理,就可能出现数据不一致、死锁等问题。因此,在多线程编程中,需要使用一些同步机制来控制线程的并发访问。

常见的线程同步机制包括:

  1. 互斥锁(Mutex):用于保护临界区,同一时间只能有一个线程进入该区域。

  2. 读写锁(Read-Write Lock):允许多个线程同时读取共享资源,但只允许一个线程写入共享资源。

  3. 信号量(Semaphore):用于限制同时访问某个资源的线程数量。

  4. 原子操作(Atomic Operations):可以保证多个线程之间的数据操作是原子性的,从而避免了数据竞争的问题。

通过使用适当的线程同步机制,可以有效地避免多线程编程中的数据竞争和死锁等问题,提高程序的可靠性和稳定性。

线程通信问题

线程通信问题是指在多线程编程中,不同线程之间如何进行通信和协调。由于多个线程可以同时运行,因此如果没有进行适当的同步和互斥操作,就可能出现数据竞争、死锁等问题。

以下是一些常见的线程通信问题:

  1. 竞态条件(Race Condition):当多个线程同时访问共享资源时,如果没有进行同步控制,就可能出现竞态条件。例如,两个线程同时对一个计数器进行递增操作,就可能导致计数器的值不正确。

  2. 死锁(Deadlock):当多个线程相互等待对方释放资源时,就可能出现死锁。例如,线程A持有资源R1并等待获取资源R2,同时线程B持有资源R2并等待获取资源R1,就会出现死锁。

  3. 饥饿(Starvation):当某个线程一直被其他线程抢占资源而无法获得足够的资源时,就可能出现饥饿。例如,线程A持有资源R1并等待获取资源R2,但同时线程B持有资源R2并一直占用,导致线程A一直无法获取资源R2。

为了解决线程通信问题,可以使用一些同步机制和工具,如互斥锁、读写锁、信号量、原子操作等。此外,还需要注意避免循环依赖、共享数据结构等问题,以确保程序的正确性和稳定性。

高级主题

  1. 线程池:线程池是一种管理和重用线程的机制。它可以提高程序的性能和稳定性,因为线程池可以控制并发线程的数量,避免线程过多导致系统资源耗尽的问题。

  2. 并发集合类:Java提供了一些并发集合类,如ConcurrentHashMap、CopyOnWriteArrayList等,它们可以在多线程环境下安全地进行操作。这些集合类的设计考虑了线程安全和性能之间的平衡。

  3. 原子操作:原子操作是一种不可中断的操作,它可以保证多个线程之间的数据操作是原子性的,从而避免了数据竞争的问题。Java提供了一些原子操作类,如AtomicInteger、AtomicLong等。

  4. 线程调度:线程调度是指操作系统如何选择和管理线程的执行时间。Java使用了一个优先级队列来管理线程的执行时间,称为调度器(Scheduler)。通过调整线程的优先级和调度策略,可以优化程序的性能和响应速度。

  5. 线程安全的容器和算法:在多线程环境下,使用不安全的容器或算法可能导致数据竞争和死锁等问题。因此,需要使用线程安全的容器和算法来保证程序的正确性和稳定性。例如,ConcurrentHashMap、CopyOnWriteArrayList、CountDownLatch等。

如上,就是对于线程的一些概念性描述和解释,我们一起作为了解,下来,我们将会进行展开详细描述!!!

二. 线程创建

image-20230524180243990

在这里,我们需要着重注意一下Runable接口

继承Thread创建线程

创建线程方式一:继承Thread类,重写run()方法,调用开启start开启线程

image-20230524191607361

基础案例一:



package com.success.day06;

public class TestThread1 extends Thread {
    /**
     * @Description 重写run方法
     * @Author IT小辉同学
     * @Date 2023/05/24
     */
    @Override
    public void run() {
        // run方法线程体
        for (int i = 0; i < 20; i++) {
            System.out.println("上天不负有心——————" + i);
        }
    }
    /**
     * @Description main主线程
     * @Author IT小辉同学
     * @Date 2023/05/24
     */
    public static void main(String[] args) {
        //创建一个线程对象
        TestThread1 thread1 = new TestThread1();
        //调用start()开启线程
        thread1.start();
        for (int i = 0; i < 2000; i++) {
            System.out.println("我们都是追梦人——————" + i);
        }
    }
}

基础案例二:

package com.success.day06;

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;

/**
 * @Description 多线程下载网络图片
 * @Author IT小辉同学
 * @Date 2023/05/24
 */
public class TestThread2 extends Thread {
    //图片链接地址
    private String imgUrl;
    //文件重命名
    private String imgName;

    public TestThread2(String imgUrl, String imgName) {
        this.imgUrl = imgUrl;
        this.imgName = imgName;
    }

    /**
     * @Description 重写run方法,添加下载任务
     * @Author IT小辉同学
     * @Date 2023/05/24
     */
    @Override
    public void run() {
        ImgDownloader downloader = new ImgDownloader();
        downloader.downloader(imgUrl, imgName);
        System.out.println("成功下载了文件:" + imgName);
    }

    /**
     * @param args
     * @Description 创建三个线程,并发
     * @Author IT小辉同学
     * @Date 2023/05/24
     */
    public static void main(String[] args) {
        TestThread2 t1 = new TestThread2("https://pic.netbian.com/uploads/allimg/161003/105551-14754633518f18.jpg", "哆啦A梦1.png");
        TestThread2 t2 = new TestThread2("https://pic.netbian.com/uploads/allimg/161003/104615-1475462775c01d.jpg", "哆啦A梦2.png");
        TestThread2 t3 = new TestThread2("https://pic.netbian.com/uploads/allimg/161003/104021-14754624214a25.jpg", "哆啦A梦3.png");
        t1.start();
        t2.start();
        t3.start();
    }
}

/**
 * @Description 图片下载器
 * @Author IT小辉同学
 * @Date 2023/05/24
 */
class ImgDownloader {
    /**
     * @param url  链接
     * @param name 名字
     * @Description 图片下载方法
     * @Author IT小辉同学
     * @Date 2023/05/24
     */
    public void downloader(String url, String name) {
        try {
            FileUtils.copyURLToFile(new URL(url), new File(name));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("文件下载异常!!!");
        }
    }
}

注:此处引入了一个依赖包

<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.11.0</version>
</dependency>
image-20230524223718088

通过运行实例我们发现,并不是按照1,2,3的顺序去下载蓝胖子,而是随机的,可见这是一个多线程并发实例!

最后,对于代码结构进行逐行剖析

  1. 定义了一个名为TestThread2的类,继承自Thread类。
  2. 在类中定义了两个成员变量:imgUrl表示图片链接地址,imgName表示文件重命名。
  3. 提供了一个构造方法,接收imgUrl和imgName参数。
  4. 在run方法中创建了一个ImgDownloader类的实例downloader。
  5. 在downloader方法中调用FileUtils的copyURLToFile方法下载网络图片到本地文件中,并打印出成功下载的信息。
  6. main方法中创建了三个TestThread2对象,分别传入不同的url和filename参数,然后调用start方法启动线程。

实现Runnable接口创建线程

image-20230524224558204

基础案例一:

package com.success.day06;

/**
 * @Description 实现Runnable接口,重写 run 方法
 * @Author IT小辉同学
 * @Date 2023/05/24
 */
public class TestRunnable1 implements Runnable {
    /**
     * @Description 重写run方法
     * @Author IT小辉同学
     * @Date 2023/05/24
     */
    @Override
    public void run() {
        // run方法线程体
        for (int i = 0; i < 20; i++) {
            System.out.println("上天不负有心——————" + i);
        }
    }

    /**
     * @Description main主线程
     * @Author IT小辉同学
     * @Date 2023/05/24
     */
    public static void main(String[] args) {
        //创建实现Runnable接口的实现类对象
        TestRunnable1 testRunnable1 = new TestRunnable1();
        //创建一个线程对象,代理
        Thread thread = new Thread(testRunnable1);
        //调用start()开启线程
        thread.start();
        //如上两句可精简为
        //new Thread((testRunnable1)).start();
        for (int i = 0; i < 2000; i++) {
            System.out.println("我们都是追梦人——————" + i);
        }
    }
}

基础案例二:

package com.success.day06;

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;

/**
 * @Description 多线程下载网络图片
 * @Author IT小辉同学
 * @Date 2023/05/24
 */
public class TestRunnable2 implements Runnable {
    //图片链接地址
    private String imgUrl;
    //文件重命名
    private String imgName;

    public TestRunnable2(String imgUrl, String imgName) {
        this.imgUrl = imgUrl;
        this.imgName = imgName;
    }

    /**
     * @Description 重写run过程,添加下载任务
     * @Author IT小辉同学
     * @Date 2023/05/24
     */
    @Override
    public void run() {
        ImgDownloader downloader = new ImgDownloader();
        downloader.downloader(imgUrl, imgName);
        System.out.println("成功下载了文件:" + imgName);
    }

    /**
     * @param args
     * @Description 创建三个线程,并发
     * @Author IT小辉同学
     * @Date 2023/05/24
     */
    public static void main(String[] args) {
        TestRunnable2 t1 = new TestRunnable2("https://pic.netbian.com/uploads/allimg/161003/105551-14754633518f18.jpg", "哆啦A梦1.png");
        TestRunnable2 t2 = new TestRunnable2("https://pic.netbian.com/uploads/allimg/161003/104615-1475462775c01d.jpg", "哆啦A梦2.png");
        TestRunnable2 t3 = new TestRunnable2("https://pic.netbian.com/uploads/allimg/161003/104021-14754624214a25.jpg", "哆啦A梦3.png");
        new Thread(t1).start();
        new Thread(t2).start();
        new Thread(t3).start();
    }
}

/**
 * @Description 图片下载器
 * @Author IT小辉同学
 * @Date 2023/05/24
 */
class ImgDownloader {
    /**
     * @param url  链接
     * @param name 名字
     * @Description 图片下载方法
     * @Author IT小辉同学
     * @Date 2023/05/24
     */
    public void downloader(String url, String name) {
        try {
            FileUtils.copyURLToFile(new URL(url), new File(name));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("文件下载异常!!!");
        }
    }
}

image-20230524230653156

注意:与第一种创建线程的方法进行对比,总结,得出自己的结论!

image-20230524230911092

提升案例一:

package com.success.day06;

/**
 * @Description 模拟多线程并发火车票售票
 * @Author IT小辉同学
 * @Date 2023/05/24
 */
public class TestRunnable3 implements Runnable{
    //火车票总数
    private int ticketNums=20;
    @Override
    public void run() {
        while (true){
            if (ticketNums<=0){
                break;
            }
            //模拟延时
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(Thread.currentThread().getName()+"获得第"+ticketNums--+"张火车票!!!");
        }
    }

    public static void main(String[] args) {
        TestRunnable3 ticket=new TestRunnable3();
        new Thread(ticket,"IT小辉同学001").start();
        new Thread(ticket,"IT小辉同学002").start();
        new Thread(ticket,"IT小辉同学003").start();
        new Thread(ticket,"IT小辉同学004").start();
        new Thread(ticket,"IT小辉同学005").start();
        new Thread(ticket,"IT小辉同学006").start();
    }
}

image-20230524232642675

这里我们就会发现多线程高并发产生了线程安全问题,后面我们解决这个问题,这里主要演示一下Runnable接口的线程实现方法!!!

提升案例二:

image-20230524233632633

package com.success.day06;

/**
 * @Description 模拟龟兔赛跑
 * @Author IT小辉同学
 * @Date 2023/05/24
 */
public class TestRunnable4 implements Runnable{
    private static String winner;
    @Override
    public void run() {
        for (int i = 0; i <=100 ; i++) {
            //模拟兔子睡觉
            if (Thread.currentThread().getName().equals("兔子")&&i%20==0){
                try {
                    //睡眠时间根据自己笔记本性能自定义
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            //判断比赛是否结束,结束停止比赛
            boolean flag=RaceGame(i);
            if (flag){
                break;
            }
            System.out.println(Thread.currentThread().getName()+"——跑了"+i+"步!!!");
        }
    }

    /**
     * @param steps 步数
     * @return boolean
     * @Description 游戏规则
     * @Author IT小辉同学
     * @Date 2023/05/24
     */
    private boolean RaceGame(int steps){
        //判断是否有胜利者
        if (winner!=null){
            return true;
        }else  {
            if (steps>=100){
                winner=Thread.currentThread().getName();
                System.out.println(winner+"胜出!!!");
                return true;
            }
        }
        return  false;
    }

    public static void main(String[] args) {
        TestRunnable4 race=new TestRunnable4();
        new Thread(race,"兔子").start();
        new Thread(race,"乌龟").start();
    }
}

image-20230524235941762

实现Callable接口创建线程

image-20230525001905270

package com.success.day07;

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;

/**
 * @Description 多线程下载网络图片
 * @Author IT小辉同学
 * @Date 2023/05/24
 */
public class TestCallable1 implements Callable<Boolean> {
    //图片链接地址
    private String imgUrl;
    //文件重命名
    private String imgName;

    public TestCallable1(String imgUrl, String imgName) {
        this.imgUrl = imgUrl;
        this.imgName = imgName;
    }

    /**
     * @Description 重写run过程,添加下载任务
     * @Author IT小辉同学
     * @Date 2023/05/24
     */
    @Override
    public Boolean call() {
        ImgDownloader downloader = new ImgDownloader();
        downloader.downloader(imgUrl, imgName);
        System.out.println("成功下载了文件:" + imgName);
        return true;
    }

    /**
     * @param args
     * @Description 创建三个线程,并发
     * @Author IT小辉同学
     * @Date 2023/05/24
     */
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        TestCallable1 t1 = new TestCallable1("https://pic.netbian.com/uploads/allimg/161003/105551-14754633518f18.jpg", "哆啦A梦1.png");
        TestCallable1 t2 = new TestCallable1("https://pic.netbian.com/uploads/allimg/161003/104615-1475462775c01d.jpg", "哆啦A梦2.png");
        TestCallable1 t3 = new TestCallable1("https://pic.netbian.com/uploads/allimg/161003/104021-14754624214a25.jpg", "哆啦A梦3.png");
        //创建服务(3个线程池)
        ExecutorService service = Executors.newFixedThreadPool(3);
        //提交执行
        Future<Boolean> r1 = service.submit(t1);
        Future<Boolean> r2 = service.submit(t2);
        Future<Boolean> r3 = service.submit(t3);
        //获取结果
        boolean rs1 = r1.get();
        boolean rs2 = r2.get();
        boolean rs3 = r3.get();
        //关闭服务
        service.shutdownNow();
    }
}

/**
 * @Description 图片下载器
 * @Author IT小辉同学
 * @Date 2023/05/24
 */
class ImgDownloader {
    /**
     * @param url  链接
     * @param name 名字
     * @Description 图片下载方法
     * @Author IT小辉同学
     * @Date 2023/05/24
     */
    public void downloader(String url, String name) {
        try {
            FileUtils.copyURLToFile(new URL(url), new File(name));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("文件下载异常!!!");
        }
    }
}

持续更新中。。。。。。

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

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

相关文章

阿里拆了中台,中台还有未来吗?

hi&#xff0c;我是熵减&#xff0c;见字如面。 近日&#xff0c;阿里在继年初3月份的16N的战略变革的基础上&#xff0c;对持续建设和运营8年的中台的调整终于落地了。 阿里对中台的这一举措&#xff0c;引发了外界对于中台战略是否还有意义的大量质疑和讨论。 甚至有人将中台…

分享一些冷门好用的网站和软件

分享一&#xff1a;UZER UZER是一个功能强大的云端应用空间&#xff0c;可以帮助您将所有的文件和应用程序都集中在一个地方&#xff0c;让您随时随地轻松访问。 以下是它的主要特点&#xff1a; 云存储&#xff1a;UZER提供大量的云存储空间&#xff0c;让您可以安全地存储…

【C++初阶】C++——模板初阶与泛型编程

​ ​&#x1f4dd;个人主页&#xff1a;Sherry的成长之路 &#x1f3e0;学习社区&#xff1a;Sherry的成长之路&#xff08;个人社区&#xff09; &#x1f4d6;专栏链接&#xff1a;C初阶 &#x1f3af;长路漫漫浩浩&#xff0c;万事皆有期待 文章目录 1. 泛型编程2. 函数模板…

C#,码海拾贝(23)——线性方程组求解的复系数方程组的全选主元高斯消去法之C#源代码,《C#数值计算算法编程》源代码升级改进版

using System; namespace Zhou.CSharp.Algorithm { /// <summary> /// 求解线性方程组的类 LEquations /// 原作 周长发 /// 改编 深度混淆 /// </summary> public static partial class LEquations { /// <summary&g…

MyBatis-Plus03_分页插件、自定义分页、乐观锁

目录 ①. 分页插件 ②. xml自定义分页 ③. 乐观锁 ①. 分页插件 ①. MyBatis Plus自带分页插件,只要简单的配置即可实现分页功能 ②. 添加配置类 Configuration public class MybatisPlusConfig {Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor(){MybatisPlu…

MyBatis-Plus02 条件构造器QueryWrapper、UpdateWrapper、Condition、LambdaQuery用法详解

目录 ①. 条件构造器与常用的接口 ②. QueryWrapper ③. UpdateWrapper ④. Condition——简化代码开发 ⑤. LambdaQueryWrapper ⑥. LambdaUpdateWrapper ①. 条件构造器与常用的接口 ①. Wrapper介绍&#xff1a; 条件构造器&#xff08;Wrapper&#xff09;的作用&…

【mmcv安装使用】

文章目录 一、前言二、mmcv安装1.安装方案a2.安装方案b 三、mmclassification使用1.目录说明2.数据集3.根据自己数据修改文件4.demo测试5.测试结果6.数据增强可视化7.日志分析 个人网站 一、前言 MMLab是香港中文大学深圳研究院的一个计算机视觉和深度学习研究团队&#xff0c…

chatgpt赋能python:PythonUSBHID:利用Python控制USB设备

Python USB HID: 利用Python控制USB设备 简介 USB HID&#xff08;Human Interface Device&#xff09;是一种USB设备类型&#xff0c;它允许用户与设备进行交互。许多设备&#xff0c;如键盘、鼠标、游戏控制器等&#xff0c;都使用USB HID协议进行通信。 Python是一种强大…

chatgpt赋能python:PythonUp:优秀的Python教育平台

Python Up: 优秀的Python教育平台 Python学习的重要性 随着大数据、人工智能等技术的蓬勃发展&#xff0c;Python作为一种可读性强、代码简洁、可扩展性好的编程语言&#xff0c;已经成为数据科学领域中最受欢迎的编程语言之一。因此&#xff0c;Python学习已成为现在最流行的…

Linux基础系统设置

文章目录 Linux基础系统设置网络设置(手动设置与DHCP自动获取)手动设置IP网络参数自动获取IP参数修改主机名 日期与时间设置时区的显示与设置时间的调整用ntpdate手动校时 语系设置防火墙简易设置案例 Linux基础系统设置 我们的CentOS 7 系统其实有很多需要设置&#xff0c;包…

什么是EMC?什么是EMI?什么是EMS?电磁兼容详解(一)

EMC&#xff08;Electro Magnetic Compatibility&#xff0c;电磁兼容&#xff09;是指电子、电气设备或系统在预期的电磁环境中&#xff0c;按设计要求正常工作的能力。它是电子、电气设备或系统的一种重要的技术性能&#xff0c;其包括三方面的含义&#xff1a; &#xff08…

离散数学_十章-图 ( 1 ):图的相关定义

&#x1f4f7;10.1 图 1. 图的定义2. 有限图 和 无限图3. 多重边、多重图4. 简单图 和 伪图5. 有向图 、无向图 、混合图5.1 简单有向图5.2 多重有向边 → 有向多重图 表1 图术语 图是一种非线性的数据结构&#xff0c;也是由顶点和连接顶点的边构成的离散结构 根据图中的边是…

lintcode-图的拓扑排序(java)

拓扑排序 拓扑排序-lintcode原题题目介绍解题思路代码演示解题方法二 (参考,不用掌握)前置知识 图的拓扑序和深度优先遍历和广度优先遍历 拓扑排序-lintcode原题 127.拓扑排序-原题链接,可以点进去测试 题目介绍 描述 给定一个有向图&#xff0c;图节点的拓扑排序定义如下: 对…

【利用AI让知识体系化】拍了拍AST

文章目录 I. 介绍什么是抽象语法树&#xff08;AST&#xff09;AST 和编译器的关系AST 在前端开发中的应用 II. 构建 AST词法分析器&#xff08;Lexer&#xff09;的作用语法分析器&#xff08;Parser&#xff09;的作用如何使用工具生成 AST III. AST 的结构AST 的节点类型如何…

深度学习 - 51.推荐场景下的 Attention And Multi-Head Attention 简单实现 By Keras

目录 一.引言 二.计算流程 1.Attention 结构 2.Multi-Head Attention 结构 三.计算实现 1.Item、序列样本生成 2.OwnAttention Layer 实现 2.1 init 初始化 2.2 build 参数构建 2.3 call 逻辑调用 3.OwnAttention Layer 测试 四.总结 一.引言 Attention And Multi…

minio在window和linux下部署权限控制添加,JAVA代码实现

minio部署启用 参考官方&#xff0c;根据不同的操作系统&#xff0c;按照步骤部署 minio官网地址https://www.minio.org.cn/docs/minio/windows/index.html minio权限添加 minio权限添加https://blog.csdn.net/xnian_/article/details/130841657 windows环境部署 安装最小I…

chatgpt赋能python:Python与USB的结合——打造更高效的设备连接

Python与USB的结合——打造更高效的设备连接 Python作为一门广泛应用于各个领域的编程语言&#xff0c;在计算机硬件设备方面也有着广泛的运用。在设备连接这一领域中&#xff0c;Python的成功案例就是其与USB的结合。 什么是USB&#xff1f; USB即通用串行总线&#xff08;…

win11 重装 NVIDIA 驱动

文章目录 win11 重装 NVIDIA 驱动1. 安装并使用驱动卸载工具 DDU2. 下载并安装 NVIDIA Toolkit3. 查看 CUDA 版本 win11 重装 NVIDIA 驱动 1. 安装并使用驱动卸载工具 DDU 浏览器搜索并下载安装 DDU win R 输入 msconfig 进入安全模式 重启后在安全模式下打开 DDU 完成卸…

讯飞星火_VS_文心一言

获得讯飞星火认知大模型体验授权&#xff0c;第一时间来测试一下效果&#xff0c;使用申请手机号登录后&#xff0c;需要同意讯飞SparkDesk体验规则&#xff0c;如下图所示&#xff1a; 同意之后就可以进行体验了&#xff0c;界面如下&#xff1a; 讯飞星火效果体验 以下Promp…

JavaScript实现循环读入整数进行累加,直到累加的和大于1000为止的代码

以下为实现循环读入整数进行累加&#xff0c;直到累加的和大于1000为止的程序代码和运行截图 目录 前言 一、循环读入整数进行累加&#xff0c;直到累加的和大于1000为止 1.1 运行流程及思想 1.2 代码段 1.3 JavaScript语句代码 1.4 运行截图 前言 1.若有选择&#xff0…