Java多线程+线程池图文实例操作(源码自取)

目录

线程相关概念

并发

 并行

继承Thread类

实现Runnable接口

实现Callable接口

使用ExecutorService 和线程池

多线程卖手机

非同步

同步机制卖手机

锁方法 

锁代码块

​编辑锁静态方法 

锁静态代码块

线程常用方法 

用户线程和守护线程

线程状态

线程池

自定义线程池

java内置线程池

newCacheThreadPool

newFixedThreadPool

 newSingleThreadExecutor

newScheduledThreadPool(int corePoolSize)

Future

模拟案例场景

手机秒杀

银行取款 

练习源码

线程相关概念

Java多线程是Java编程语言中一个重要的特性,它允许程序同时执行多个线程,从而实现并发执行任务,提高程序的性能和响应性。

基础概念

  • 线程: 线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。Java中,每一个线程都是一个独立的执行流,拥有自己的堆栈和局部变量。

  • 并发与并行: 并发指的是在同一时间段内,多个任务交替执行,看起来像是同时执行的;并行则是指真正的同时执行多个任务,这通常需要多核处理器的支持。

并发

并发是在同一个时刻,多个任务在交替执行,一种"貌似同时"在执行的错觉,因为计算机执行速度太快,所以会产生这样的错觉,其实就是 单核cpu实现的多任务就是并发

 并行

在同一个时刻,多个任务同时进行,多核cpu可以实现并行

继承Thread类

  • 创建一个新的类继承 Thread 类,并重写 run() 方法。在 run() 方法中编写线程要执行的代码。
  • 优点:可以直接访问并扩展 Thread 类的方法和属性,如 interrupt() 或 getState()
  • 缺点:如果想要扩展其他类,就不能使用此方法,因为Java不支持多重继承。

java中普通类继承Thread可以实现多线程,Thread类其实也是实现了Runnable接口来实现多线程的

启动查看效果

 可以看到打印的日志信息线程名字是在子线程和main线程之间来回进行切换,从而得出并发的效果

而run方法其实只是一个普通方法,使用start()方法才会启动线程的并发

如果使用的是run方法看下是怎样的效果

启动查看

 可以看到并没有交替执行,且子线程打印的线程名也一直是main的名字,run方法只是一个普通方法,并不会启动多线程

实现Runnable接口

  • 创建一个新的类实现 Runnable 接口,并实现 run() 方法。
  • 将这个 Runnable 实例传递给 Thread 构造函数创建 Thread 对象,然后调用 start() 方法启动线程。
  • 优点:可以让你的类继承自其他类,同时实现多线程功能。
  • 缺点:相比直接继承 Thread 类,你必须额外创建一个 Thread 对象。

由于java是单继承多实现,当我们使用了java中某一个类,而当前类又继承了某一个父类,此时如果想再用该类去实现多线程就不能再去继承Thread类来进行实现多线程了,因为java是单继承多实现的

可以采用实现Runnable接口来实现多线程

查看效果

实现Callable接口

  • Callable 和 Runnable 类似,但是 Callable 的 call() 方法可以返回一个结果并且可以抛出异常。
  • 需要创建一个 Callable 接口的实现类,然后将其封装进 FutureTask 中,最后将 FutureTask 传给 Thread 构造函数或直接交给 ExecutorService 执行。
  • 优点:提供了一种可以获取线程执行结果的方式。
  • 缺点:实现比 Runnable 更复杂。

import java.util.concurrent.*;

public class IphoneCallable implements Callable<Integer> {


    // 定义手机数量为100台
    private static int count = 100;
    private static final Object lock = new Object();

    @Override
    public Integer call() throws Exception {
        int sold = 0;
        while (true) {
            synchronized (lock) {
                if (count <= 0) {
                    System.out.println(Thread.currentThread().getName()
                            + " 库存为0, 停止售卖");
                    return sold;
                }
                System.out.println(Thread.currentThread().getName()
                        + " Callable 窗口卖手机,剩余手机数量:" + --count);
                sold++;
                Thread.sleep(10);
            }
        }
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executor = Executors.newFixedThreadPool(3);
        Future<Integer>[] futures = new Future[3];
        for (int i = 0; i < 3; i++) {
            futures[i] = executor.submit(new IphoneCallable());
        }

        int totalSold = 0;
        for (Future<Integer> future : futures) {
            totalSold += future.get();
        }

        System.out.println("总共售出手机:" + totalSold);
        executor.shutdown();
    }
}

 可以看到获取了线程最后的返回值

使用ExecutorService 和线程池

  • ExecutorService 是 Java 提供的一种用于管理和控制线程的高级工具,它可以复用一组线程来执行任务。
  • 通过 Executors 工厂方法创建 ExecutorService 实例,然后使用 submit() 方法提交 Runnable 或 Callable 任务。
  • 优点:可以更有效地管理线程资源,避免了频繁创建和销毁线程的开销,提高了系统性能。
  • 缺点:相比前三种方法,使用 ExecutorService 的代码更为复杂,且需要理解线程池的工作原理。
  • import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class IphoneRunnable implements Runnable {
    
        // 定义手机数量为100台
        private static int count = 100;
        private static final Object lock = new Object();
        private static final int THREAD_COUNT = 3;
        private static volatile boolean shouldRun = true;
    
        @Override
        public void run() {
            while (shouldRun) {
                synchronized (lock) {
                    if (count <= 0) {
                        System.out.println(Thread.currentThread().getName()
                                + " 库存为0, 停止售卖");
                        shouldRun = false;
                        return;
                    }
                    System.out.println(Thread.currentThread().getName()
                            + " Runnable 窗口卖手机,剩余手机数量:" + --count);
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                }
            }
        }
    
        public static void main(String[] args) {
            ExecutorService executor = Executors.newFixedThreadPool(THREAD_COUNT);
            for (int i = 0; i < THREAD_COUNT; i++) {
                executor.execute(new IphoneRunnable());
            }
            executor.shutdown();
        }
    }

多线程卖手机

非同步

 下面以多个窗口卖手机为例,查看下多线程情况下卖手机会有什么问题

启动查看结果

 发现当手机库存为0后Thread-2和Thread-0还在进行卖,从而出现了-1的超卖现象,这种情况下肯定是不允许的

下面以实现Runnable接口查看现象看下

 也出现了超卖现象

同步机制卖手机

锁方法 

 启动测试

多次测试没有出现超卖现象 

锁代码块

synchronoized不仅可以写在方法上,还可以单独包裹代码块

或者定义一个对象,然后锁该对象,要保证锁的是同一个对象即可

锁静态方法 

如果是静态方法,则锁的是当前的类本身

锁静态代码块

锁静态方法的代码块,也是锁定当前类本身

线程常用方法 

  1. start(): 启动线程,使其开始执行线程的任务。
  2. run(): 线程的任务代码,定义在线程中要执行的操作。
  3. join(): 在一个线程中调用另一个线程的join()方法,会让当前线程等待被调用线程执行完毕后再继续执行。
  4. sleep(long milliseconds): 使线程暂停执行指定的时间,以毫秒为单位。
  5. yield(): 使当前线程让出CPU执行权,让同优先级的线程有机会执行。
  6. interrupt(): 中断线程,发送一个中断信号给线程,使其退出阻塞状态。
  7. isAlive(): 检测线程是否还存活。
  8. setPriority(int priority): 设置线程的优先级,优先级越高,被执行的可能性越大。
  9. getPriority():获取线程优先级

用户线程和守护线程

用户线程: 也叫工作线程,当线程的任务执行完成或通知方式结束

守护线程: 一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束

常见的守护线程:垃圾回收机制

先来看下没有守护线程的情况下主线程和子线程的执行关系

启动查看

 可以看到当主线程停止了辛苦的工作后,子线程还在执行愉快的玩耍,没有和主线程共同进退,此时就需要使用守护进程了

再次启动查看效果

 可以看到当设置子线程为守护线程后,主线程停止后,子线程也自动停止了执行

线程状态

  1. NEW(新建): 当线程对象被创建时,它处于新建状态。此时线程尚未开始执行。
  2. RUNNABLE(可运行 可细分为 ready(准备) 和runing(运行)): 线程对象创建后,其他线程调用了该对象的start()方法,线程进入可运行状态。处于可运行状态的线程可能正在等待CPU的调度执行,也可能正在执行。
  3. BLOCKED(阻塞): 当线程在等待获取同步锁时,如果获取不到(因为其他线程已经持有了锁),该线程会进入阻塞状态。
  4. WAITING(等待): 线程进入等待状态,等待其他线程的通知或者唤醒。
  5. TIMED_WAITING(计时等待): 线程进入计时等待状态,等待一定的时间后自动唤醒。
  6. TERMINATED(终止): 线程执行完任务或者因异常退出后,进入终止状态

看下线程状态演化图

代码打印其各个状态

 

 

 Waitiing状态没有打印出来,可以自己尝试下

线程池

线程池其实就是一种多线程处理形式,处理过程中可以将任务添加到队列中,然后在创建线程后自动启动这些任务。这里的线程就是前面的线程,任务就是实现了Runnable或Callable接口的实例对象;

使用线程池最大的原因就是可以根据系统的需求和硬件环境灵活的控制线程的数量且可以对所有线程进行统一的管理和控制,从而提高系统的运行效率,降低系统运行运行压力;

线程和任务分离,提升线程重用性;

控制线程并发数量,降低服务器压力,统一管理所有线程;

提升系统响应速度,假如创建线程用的时间为T1,执行任务用的时间为T2,销毁线程用的时间为T3,那么使用线程池就免去了T1和T3的时间

自定义线程池

自定义线程类

import java.util.List;

/*
    需求:
        编写一个线程类,需要继承Thread类,设计一个属性,用于保存线程的名字;
        设计一个集合,用于保存所有的任务;
 */
public class MyWorker extends Thread{
    private String name;//保存线程的名字
    private List<Runnable> tasks;
    //利用构造方法,给成员变量赋值

    public MyWorker(String name, List<Runnable> tasks) {
        super(name);
        this.tasks = tasks;
    }

    @Override
    public void run() {
        //判断集合中是否有任务,只要有,就一直执行任务
        while (tasks.size()>0){
            Runnable r = tasks.remove(0);
            r.run();
        }
    }
}

自定义任务类


/*
    需求:
        自定义线程池练习,这是任务类,需要实现Runnable;
        包含任务编号,每一个任务执行时间设计为0.2秒
 */
public class MyTask implements Runnable{
    private int id;
    //由于run方法是重写接口中的方法,因此id这个属性初始化可以利用构造方法完成
    private static boolean loop = true;
    private static int count=100;

    public void setId(int id){
        this.id = id;
    }

    @Override
    public void run() {
        // 判断手机数量是否大于0
        while (loop) {
            extracted();
        }
    }
    private  synchronized static void extracted() {
        if(count<=0){
            System.out.println(Thread.currentThread().getName()
                    + "库存为0,停止售卖");
            loop = false;
            return;
        }
        System.out.println(Thread.currentThread().getName()
                + "Runnable窗口卖手机,剩余手机数量:" + --count);
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public String toString() {
        return "MyTask{" +
                "id=" + id +
                '}';
    }
}

自定义线程池


import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

/*
    这是自定义的线程池类;

    成员变量:
        1:任务队列   集合  需要控制线程安全问题
        2:当前线程数量
        3:核心线程数量
        4:最大线程数量
        5:任务队列的长度
    成员方法
        1:提交任务;
            将任务添加到集合中,需要判断是否超出了任务总长度
        2:执行任务;
            判断当前线程的数量,决定创建核心线程还是非核心线程
 */
public class MyThreadPool {
    // 1:任务队列   集合  需要控制线程安全问题
    private List<Runnable> tasks = Collections.synchronizedList(new LinkedList<>());
    //2:当前线程数量
    private int num;
    //3:核心线程数量
    private int corePoolSize;
    //4:最大线程数量
    private int maxSize;
    //5:任务队列的长度
    private int workSize;

    public MyThreadPool(int corePoolSize, int maxSize, int workSize) {
        this.corePoolSize = corePoolSize;
        this.maxSize = maxSize;
        this.workSize = workSize;
    }

    //1:提交任务;
    public void submit(Runnable r){
        //判断当前集合中任务的数量,是否超出了最大任务数量
        if(tasks.size()>=workSize){
            System.out.println("任务:"+r+"被丢弃了...");
        }else {
            tasks.add(r);
            //执行任务
            execTask(r);
        }
    }
    //2:执行任务;
    private void execTask(Runnable r) {
        //判断当前线程池中的线程总数量,是否超出了核心数,
        if(num < corePoolSize){
            new MyWorker("核心线程:"+num,tasks).start();
            num++;
        }else if(num < maxSize){
            new MyWorker("非核心线程:"+num,tasks).start();
            num++;
        }else {
            System.out.println("任务:"+r+" 被缓存了...");
        }
    }

}

测试

/*
    测试类:
        1: 创建线程池类对象;
        2: 提交多个任务
 */
public class MyTest {
    public static void main(String[] args) {
        //1:创建线程池类对象;
        MyThreadPool pool = new MyThreadPool(2,4,20);
        //2: 提交多个任务
        MyTask my = new MyTask();
        for (int i = 0; i <30 ; i++) {
            //3:创建任务对象,并提交给线程池
            my.setId(i);
            pool.submit(my);
        }
    }
}

启动测试:

可以看到自定义的线程池也没有出现超卖

java内置线程池

java中使用ExecutorService可以利用jdk中的Executors类中的静态方法获取线程池

newCacheThreadPool

  • 作用: 创建一个可缓存的线程池,线程数动态调整。
  • 优点: 当任务完成后,空闲线程会被回收,当需要时,又可以迅速恢复线程,适用于执行大量短时间的任务。

创建一个默认的线程池对象 里面的线程可重用

还可以传递自定义的线程工厂来实现

 

newFixedThreadPool

  • 作用: 创建一个固定大小的线程池。
  • 优点: 线程数量固定,可以有效控制资源使用,避免大量线程创建和销毁带来的性能开销。

创建可重用固定线程数的线程池

 同样也可以指定线程数

测试

 newSingleThreadExecutor

  • 作用: 创建一个单线程化的线程池。
  • 优点: 确保所有任务按照指定顺序执行,可以用于需要保持任务执行顺序的场景。

创建一个使用的那个worker线程的Executor,以误界队列方式以来运行该线程

也可以指定线程工厂来进行创建任务

 

也是只有一个线程 

newScheduledThreadPool(int corePoolSize)

  • 作用: 创建一个定时的线程池,支持定时和周期性任务执行。
  • 优点: 可以执行定时任务,非常适合于需要定期执行的任务场景。

启动查看效果

可以看到程序先输出了over,有了2秒延迟后才开始执行任务

Future

Future 是 Java 中用于表示异步计算结果的一个接口,它通常与 ExecutorServiceCallable 结合使用

当我们在执行异步任务时,有时会遇到需要拿取任务返回值的情况,此时就需要用Future和Callable接口来进行任务书写

模拟案例场景

手机秒杀

 创建线程任务

/*
    任务类:
        包含了商品数量,客户名称,送手机的行为;
 */
public class Mytask implements Runnable {
    //设计一个变量,用于表示商品的数量
    private static int id = 10;
    //表示客户名称的变量
    private String userName;

    public Mytask(String userName) {
        this.userName = userName;
    }

    @Override
    public void run() {
        String name = Thread.currentThread().getName();
        System.out.println(userName+"正在使用"+name+"参与秒杀任务...");
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (Mytask.class){
            if(id>0){
                System.out.println(userName+"使用"+name+"秒杀:"+id-- +"号商品成功啦!");
            }else {
                System.out.println(userName+"使用"+name+"秒杀失败啦!");
            }
        }
    }
}

创建测试入口

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/*
    主程序类,测试任务类
 */
public class Mytest {
    public static void main(String[] args) {
        //1:创建一个线程池对象
        ThreadPoolExecutor pool = new ThreadPoolExecutor(3,5,1,
                TimeUnit.MINUTES,new LinkedBlockingQueue<>(15));
        //2:循环创建任务对象
        for (int i = 1; i <=20 ; i++) {
            Mytask myTask = new Mytask("客户"+i);
            pool.submit(myTask);
        }
        //3:关闭线程池
        pool.shutdown();
    }
}

启动测试

银行取款 

编写线程任务

public class MyTask implements Runnable {
    //用户姓名
    private String userName;
    //取款金额
    private double money;
    //总金额
    private static double total = 1000;

    public MyTask(String userName, double money) {
        this.userName = userName;
        this.money = money;
    }

    @Override
    public void run() {
        String name = Thread.currentThread().getName();
        System.out.println(userName+"正在准备使用"+name+"取款:"+money+"元");
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (MyTask.class){
            if(total-money>0){
                System.out.println(userName+"使用"+name+"取款:"+money+"元成功,余额:"+(total-money));
                total-=money;
            }else {
                System.out.println(userName+"使用"+name+"取款:"+money+"元失败,余额:"+total);
            }
        }
    }
}

启动入口

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;

public class MyTest {
    public static void main(String[] args) {
        //1:创建线程池对象
        ExecutorService pool = Executors.newFixedThreadPool(2, new ThreadFactory() {
            int id = 1;

            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r, "ATM" + id++);
            }
        });
        //2:创建两个任务并提交
        for (int i = 1; i <=2 ; i++) {
            MyTask myTask = new MyTask("客户" + i, 800);
            pool.submit(myTask);
        }
        //3:关闭线程池
        pool.shutdown();
    }
}

 

练习源码

 练习源码

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

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

相关文章

Ubuntu/Linux系统安装JDK1.8(带jdk1.8资源和操作教程)

文章目录 前言一、JDK1.8下载二、上传三、安装四、配置环境变量五、查看总结 前言 &#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;Ubuntu/Linux jdk1.8安装包&#xff…

手机铃声下载2个必备技巧,定制化铃声,彰显个性魅力

手机铃声&#xff0c;就像是独特的信号灯&#xff0c;不仅仅是通知我们来电或信息的方式&#xff0c;更是展现个人品位和魅力的武器。手机铃声下载和定制&#xff0c;让你的手机从千万舰队中脱颖而出。在接下来的文章中&#xff0c;我们将详细探讨铃声下载技巧的具体操作步骤&a…

第二届人工智能、系统与网络安全国际学术会议 (AISNS 2024)

第二届人工智能、系统与网络安全国际学术会议 (AISNS 2024&#xff09; 2024 2nd International Conference on Artificial Intelligence, Systems and Network Security 一、重要信息 大会官网&#xff1a;www.aisns.org &#xff08;点击参会/投稿/了解会议详情&#xff09…

【Java】已解决java.sql.SQLTimeoutException异常

文章目录 一、分析问题背景二、可能出错的原因三、错误代码示例四、正确代码示例五、注意事项 已解决java.sql.SQLTimeoutException异常 在Java的数据库编程中&#xff0c;java.sql.SQLTimeoutException是一个重要的异常&#xff0c;它通常表示在数据库操作&#xff08;如查询…

Java Array示例说明

Java Array示例说明 数组是相同类型的元素的集合。例如&#xff0c;int数组包含整数元素&#xff0c;String数组包含String元素。Array的元素存储在内存中的相邻位置。Java中的数组基于零基索引系统&#xff0c;这意味着第一个元素位于索引0处。 数组如下所示&#xff1a; i…

C++回溯算法(2)

棋盘问题 #include<bits/stdc.h> using namespace std; void func(int,int); bool tf(int,int); void c(); int n,k; char a[110][110]; int cnt20; int main() {cin>>n>>k;for(int i0;i<n;i){for(int j0;j<n;j){cin>>a[i][j];}}func(0,0);cout…

企业PC端官网在线客服源码系统 完全开源可二开 附带源代码包+搭建部署教程

系统概述 企业 PC 端官网在线客服源码系统是一款专为企业打造的先进客服解决方案。它基于先进的技术架构&#xff0c;旨在为企业提供稳定、高效、功能丰富的在线客服服务。 该系统采用了模块化设计理念&#xff0c;将各个功能模块有机地整合在一起&#xff0c;形成了一个完整…

GPT大模型不再遥不可及:本地化部署让每个人都能拥有

01、本地化部署是GPT发展的一个趋势 我们提到大模型就想到这个东西不是我们普通人可以拥有的&#xff0c;因为太耗费服务器资源&#xff0c;注定了可以提供大模型服务的只能是大厂。 然而有需求就会有解决方案&#xff0c;那就是让大语言模型对特定地区的行业和专业领域有较强…

时间复杂度的相关概念

1. 统计时间增长趋势 时间复杂度分析统计的不是算法运行时间&#xff0c;而是算法运行时间随着数据量变大时的增长趋势&#xff0c;也就是算法运行时间与输入数据的关系。 // 算法 A 的时间复杂度&#xff1a;常数阶 function algorithm_A(n) {console.log(0); } // 算法 B 的…

二叉树(数据结构篇)

数据结构之二叉树 二叉树 概念&#xff1a; 二叉树(binary tree)是一颗每个节点都不能多于两个子节点的树&#xff0c;左边的子树称为左子树&#xff0c;右边的子树称为右子树 性质&#xff1a; 二叉树实际上是图&#xff0c;二叉树相对于树更常用。 平衡二叉树的深度要比…

(3) cmake编译多个cpp文件

文章目录 概要整体代码运行结果 概要 上一节中实现了对单个cpp文件用cmake编译。这一节升级一下 整体代码 main.cpp #include <iostream> #include "person.h"using namespace std;int main() {person me person("langdaoliu", 28, "engin…

nuc算法设计与分析 ppt总结

总纲 插入排序算法 内容&#xff1a; 将数组待排序的元素依次插入到已排序部分&#xff0c;使已排序部分保持升序的性质。 伪代码&#xff1a; 复杂度分析&#xff1a; 时间复杂度为O(n^2)&#xff0c;空间复杂度为O(1)。在数据量较小的情况下&#xff0c;插入排序的效率不输给…

Linux服务器挖矿病毒处理

文章目录 Linux服务器挖矿病毒处理1.中毒表现2.解决办法2.1 断网并修改root密码2.2 找出隐藏的挖矿进程2.3 关闭病毒启动服务2.4 杀掉挖矿进程 3. 防止黑客再次入侵3.1 查找异常IP3.2 封禁异常IP3.3 查看是否有陌生公钥 补充知识参考 Linux服务器挖矿病毒处理 情况说明&#x…

echarts dataZoom用按钮代替鼠标滚轮实现同样效果

2024.06.19今天我学习了echarts dataZoom如何用按钮来控制放大缩小的功能&#xff0c; 效果如下&#xff1a; 通过控制按钮来实现图表放大缩小数据的效果。 步骤如下&#xff1a; 一、写缩放按钮&#xff0c;以及图表数据。 二、设置初始位置的变量&#xff0c;我这边是七个…

InPixio Photo Cutter v10 解锁版安装教程 (懒人抠图工具)

前言 InPixio Photo Cutter是一款懒人抠图工具&#xff0c;采用了增强的算法切割技术&#xff0c;可以在不影响图像质量的情况下&#xff0c;允许用户从照片中删除任何物体或人物&#xff0c;并且保持其完整的质量。你只需点击几下鼠标&#xff0c;便可从照片中剪下任何细节、…

上位机图像处理和嵌入式模块部署(h750 mcu中的pwm控制)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 所谓的pwm&#xff0c;其实就是方波。我们都知道&#xff0c;对于一个电机来说&#xff0c;如果插上正负极的话&#xff0c;那么电机就会全速运转。…

C#.Net筑基-集合知识全解

01、集合基础知识 .Net 中提供了一系列的管理对象集合的类型&#xff0c;数组、可变列表、字典等。从类型安全上集合分为两类&#xff0c;泛型集合 和 非泛型集合&#xff0c;传统的非泛型集合存储为Object&#xff0c;需要类型转。而泛型集合提供了更好的性能、编译时类型安全…

spring cloud Alibaba 整合 seata AT模式

准备工作&#xff1a; 1、MySQL正常安装并启动 2、nacos正常部署并启动 3、下载 Seata-1.4.2 源码包和 seata-server-1.4.2 服务端源码包&#xff08;版本根据自己的需要选择&#xff0c;我这里选择1.4.2&#xff09; 下载地址&#xff1a; Seata&#xff1a;https://gite…

PFA托盘400*300*42mm耐酸碱透明聚四氟乙烯方盘方槽耐高温厂家供

PFA方盘又称托盘&#xff1a;耐高温、耐腐蚀。 进口透明可溶性聚四氟乙烯方盘。可应用于成膜实验&#xff0c;样品液体脱漏等。能放在电热板上直接加热使用&#xff0c;也可以用于烘箱烘干&#xff0c;实验室腐蚀性样品的转移和搬运&#xff0c;防止腐蚀性液体洒落。 产品特性…

计算机网络 —— 应用层(FTP)

计算机网络 —— 应用层&#xff08;FTP&#xff09; FTP核心特性&#xff1a;运作流程&#xff1a; FTP工作原理主动模式被动模式 我门今天来看应用层的FTP&#xff08;文件传输协议&#xff09; FTP FTP&#xff08;File Transfer Protocol&#xff0c;文件传输协议&#x…