【JavaEE】Java多线程编程案例 -- 多线程篇(3)

news2024/12/29 13:34:57

Java多线程编程案例

  • 1. 单例模式
    • 1.1 代码的简单实现
    • 1.2 懒汉模式的线程安全代码
  • 2. 阻塞队列
    • 2.1 阻塞队列的概念
    • 2.2 使用库中的BlockingDeque
    • 2.3 模拟实现阻塞队列
    • 2.4 生产者消费者模型
  • 3. 定时器
    • 3.1 概念
    • 3.2 使用库的定时器 - Timer类
    • 3.3 模拟实现定时器
  • 4. 线程池
    • 4.1 概念
    • 4.2 使用库中的线程池
    • 4.3 线程池模拟实现

1. 单例模式

1.1 代码的简单实现

  • 应用场景: 一个项目中, 该对象只能创建一个

饿汉模式 – 迫切, 程序启动, 类加载之后, 立即创建出实例

代码示例

class SingletonHungryMode { // 不加以任何限制就是线程安全的
    private static SingletonHungryMode instance = new SingletonHungryMode(); // 直接new

    public static SingletonHungryMode getInstance() {
        return instance;
    }
    // 添加限制, 让外部无法 new 出对象
    private SingletonHungryMode() {
    }
}

优点:

  • 编写代码简单

缺点:

  • 一开始就需要加载对象, 会降低程序的启动速率, 一开始不需要用到该对象的时候, 就会体验感下降

懒汉模式 - 正在需要用到实例的时候才创建对象

class SingletonLazyMode {
    private static SingletonLazyMode instance = null; // 不是直接new

    // 这个版本不是线程安全的 
    public static SingletonLazyMode getInstance() {
        if (instance == null) {
            instance = new SingletonLazyMode();
        }
        return instance;
    }

    // 添加限制, 让外部无法 new 出对象
    private SingletonLazyMode() {
    }
}

优点:

  • 可以在需要的时候在new出实例对象, 可以提高程序界面的加载速度

Java的反射与单例模式的思考

我们都知道, Java提供了反射机制, 通过反射, 我们可以得到类的所有信息, 可以得到private修饰的构造函数, 就可以new 多个对象, 这样单例模式中private修饰;

那么我们通过private修饰构造方法设计的单例模式是不是就存在问题呢? 是的! 使用反射, 确实可以在当前单例模式中, 创建出多个实例;

反射是属于 “非常规” 的编程手段, 正常开发的时候, 不应该使用/慎用; 滥用反射, 会带来极大的风险, 会让代码变的抽象, 难以维护!

Java 中也有实现单例模式而不怕反射的

1.2 懒汉模式的线程安全代码

懒汉模式下线程不安全的原因

在这里插入图片描述

解决方案 1)

// 版本2 加锁保证, 但是存在频繁加锁的问题 -- 效率低
public static SingletonLazyMode getInstance() {
    synchronized (locker) {
        if (instance == null) {
            instance = new SingletonLazyMode();
        }
    }
    return instance;
}
  • 加锁就保证了线程安全了
  • 效率分析
    • 加锁是一个成本比较高的操作, 教唆可能会引起阻塞
    • 加锁的基本原则, 应该是, 非必要, 不加锁, 不能无脑加锁, 如果无脑加锁, 就会导致程序执行效率受到影响。
    • 上述代码除了要保证创建对象的时候需要保证 if语句是原子, 剩下的时候条件都为 false, 所以此时的加锁就很重

解决方案 2)

// 版本3 双重判断, 避免无脑加锁 -- 效率高
public static SingletonLazyMode getInstance() {
    if (instance == null) { // 条件判断是否需要加锁
        synchronized (locker) {
            if (instance == null) { // 条件判断是否需要创建新的对象
                instance = new SingletonLazyMode();
            }
        }
    }
    return instance;
}
  • 这样基本可以保证线程安全了
  • 但是还存在一个特别的情况, 内存可见性的问题!!!

在这里插入图片描述

  • volatile 还有一个功能, 避免指令的重排序的问题
    • 指令重排序也是编译器优化一种首单
    • 保证原有的逻辑不变的前提下, 对代码执行顺序进行调整, 调整之后的执行效率提高。
    • 如果是单线程, 这样的重排序, 一般没事
    • 如果是多线程, 就可能出现问题了
指令重排序可能出现的问题
  • 对于Instance = new SingletonLazy()指令步骤
    1. 给对象创建出内存空间, 得到内存地址
    2. 在空间上调用构造方法, 对对象进行初始化
    3. 把内存地址, 赋值给 Instance 引用
  • 此处就可能涉及到指令重排序
    • 1 2 3 -> 132
    • 如果是单个线程, 此时无所谓, 但是多线程就不一定了

在这里插入图片描述

  • 给Instance加上 volatile 之后, 此时针对 Instance 进行的赋值操作, 就不会产生上述的指令重排序了, 必然按照 1 2 3 顺序执行!

解决方案 3) – 最终版本

 private static volatile SingletonLazyMode instance = null; // 不是直接new

 // 版本3 双重判断, 避免无脑加锁 -- 效率高
 public static SingletonLazyMode getInstance() {
     if (instance == null) { // 条件判断是否需要加锁
         synchronized (locker) {
             if (instance == null) { // 条件判断是否需要创建新的对象
                 instance = new SingletonLazyMode();
             }
         }
     }
     return instance;
 }

 // 添加限制, 让外部无法 new 出对象
 private SingletonLazyMode() {
 }
  • Java中实现单例模式的三个关键点
    1. 加锁
    2. 双重if
    3. volatile

2. 阻塞队列

2.1 阻塞队列的概念

  • 阻塞队列, 带有阻塞功能
    1. 当队列满的时候, 继续入队列, 就会出现阻塞, 阻塞到其它线程从队列中取走元素为止
    2. 当队列空的时候, 继续出队列, 也会出现阻塞, 阻塞到其它线程往队列中添加元素为止

2.2 使用库中的BlockingDeque

  • 两个关键的方法
    • put 入队列 – 具有阻塞功能
    • take 出队列 – 具有阻塞功能

【使用示例】

 public static void main(String[] args) throws InterruptedException {
     BlockingDeque<String> queue = new LinkedBlockingDeque<>(10);

     // put 入队列, take 出队列  -- 这两个方法有阻塞的功能
     queue.put("Hello BlockingDeque");
     String elem = queue.take();
     System.out.println(elem);
     elem = queue.take();
     System.out.println(elem);

     // offer 入队列, poll 出队列 -- 这两个方法没有阻塞的功能
     // queue.offer("test");
     // System.out.println(queue.poll());
     // System.out.println(queue.poll());
 }

2.3 模拟实现阻塞队列

public class MyBlockingDeque {
    // 使用一个 String 类型的数组来保存元素. 假设这里只存 String.
    private String[] strings;
    // 指向队列的头部
    private int head;
    // 指向队列的尾部的下一个元素. 总的来说, 队列中有效元素的范围 [head, tail)
    // 当 head 和 tail 相等(重合), 相当于空的队列.
    private int tail;
    // 使用 size 来表示元素个数.
    private int size;
    private final static int DEFAULT_CAPACITY = 1000;

    // 加锁对象
    private Object locker;

    public MyBlockingDeque() {
        this(DEFAULT_CAPACITY);
    }

    public MyBlockingDeque(int capacity) {
        strings = new String[capacity];
        head = tail = size = 0;
        locker = new Object();
    }

    public void put(String str) throws InterruptedException {
        synchronized (locker) {
            // if (isFull()) {
            while (isFull()) { // 循环判断, 保证醒来的时候队列不满了
                // 队列满, 进行wait等待
                locker.wait();
                // return;
            }

            strings[tail] = str;
            ++tail;
            if (tail >= strings.length) {
                tail = 0;
            }

            ++size;
            locker.notify(); // 生产完, 唤醒消费消费者进行消费
        }
    }

    public String take() throws InterruptedException {
        synchronized (locker) {
            while (isEmpty()) {
                locker.wait(); // 等待生产者生产
                // return null;
            }
            String str = strings[head];
            ++head;
            if (head >= strings.length) {
                head = 0;
            }

            --size;
            locker.notify();

            return str;
        }
    }


    private boolean isFull() {
        return size == strings.length;
    }

    private boolean isEmpty() {
        return size == 0;
    }
}
  • wait方法的注意事项
    • wait方法醒来的时候, 条件不一定就绪了
    • 被notify唤醒的时候, 一定要用循环条件来判断条件是否成立, 这样才能保证醒来的时候, 条件已经就绪了

2.4 生产者消费者模型

生产者消费者模型的优势

  1. 解耦合

    • 解耦合就是 “降低模块之间的耦合”
    • 通过一个 “交易场所” 来时保证
      • 例如可以通过阻塞队列
      • 有了这个中间交易场所, 对于生产者来说, 只需要关注生产, 生产出来的任务一股脑放进这个交易场所中即可; 如果交易场所满了, 就会告知生产者, 生产者就会阻塞等待消费者行消费
      • 对于消费者来说, 只需要一股脑从交易场所中取出任务即可了, 当没有任务的时候, 交易场所会告知消费者, 消费者就会进行阻塞等待生产者生产任务
      • 所以这样, 如果消费者出问题了, 也不会影响到生产者, 相反也是一样的, 最多也就是阻塞等待而已; 这样就实现了解耦合操作
      • 这个交易场所也可以加入更多的消费者来消费, 更多的生产者来生成, 他们之间都是互相不受影响的
  2. 削峰填谷

    • 如果生产者生成能力大于消费者消费能力, 当生产者把交易场所填满的时候就会阻塞等待消费者消费 – 这样就使得生产者和消费者步调一致了
生产者消费者示例
public static void main(String[] args) {
      BlockingDeque<Integer> queue = new LinkedBlockingDeque<>(100);

      Thread consumer = new Thread(() -> {
          while (true) {
              try {
                  Integer task = queue.take();
                  System.out.println("消费:" + task);
                  // Thread.sleep(1000);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
      });

      Thread producer = new Thread(() -> {
          int count = 0;
          while (true) {
              try {
                  Thread.sleep(1000);
                  queue.put(count);
                  System.out.println("生产: " + count);
                  ++count;
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
      });

      consumer.start();
      producer.start();
  }

3. 定时器

3.1 概念

  • 定时器就相当于一个闹钟, 在未来某个时间去做某件事, 起到提醒的作用

3.2 使用库的定时器 - Timer类

  • 标准库中提供了一个 Timer 类, Timer 类的核心方法为 schedule
  • schedule 包含两个参数, 第一个参数指定即将要执行的任务代码, 第二参数指定多长时间后执行 (单位为毫秒);

public static void main(String[] args) {
     Timer timer = new Timer();

     timer.schedule(new TimerTask() {
         @Override
         public void run() {
             System.out.println("Hello 3");
         }
     }, 3000);

     timer.schedule(new TimerTask() {
         @Override
         public void run() {
             System.out.println("Hello 2");
         }
     }, 2000);

     timer.schedule(new TimerTask() {
         @Override
         public void run() {
             System.out.println("Hello 1");
         }
     }, 1000);

     System.out.println("程序开始运行!");
 }
  • Timer 内部, 有自己的线程
  • 为了保证随时可以处理新安排的任务, 这个线程会持续执行, 并且这个线程还是个前台线程

3.3 模拟实现定时器

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

4. 线程池

4.1 概念

  • 池的作用: 就是提高效率的

    • 有 线程池
    • 内存池
    • 进程池
    • 常量池
  • 线程池的作用

    • 如果我们需要频繁的创建销毁线程, 此时创建销毁线程的成本, 就不能忽视了, 因此就可以使用线程池
    • 提前创建好一波线程, 后续需要使用下层, 就直接从池子里拿一个一个即可
    • 当线程不在使用, 就放回池子里面
    • 这样就可以避免频繁的创建和销毁线程了
  • 本来, 是需要创建线程/销毁线程; 现在, 是从池子里获取现成的线程, 并且把用完的线程归还到池子中

    • 为啥, 从池子里取, 就比从系统这里创建线程更快更高效呢?
      • 如果从系统这里创建线程, 需要调用系统 API, 进一步的由操作系统内核完成线程的创建过程 (内核是给所有进程提供服务的) – 这不可控
      • 如果从线程池里面获取现成, 上述的内核中进行的操作, 都提前做好了, 现在的取线程的过程, 纯粹的由用户代码完成(纯用户态) 这是可控的

4.2 使用库中的线程池

工程模式

  • 工厂使用来生产的, 所以工程模式是用来生产对象的
  • 设计原因
    • 一般创建对象, 都是通过new, 通过构造方法, 但是构造方法, 存在重大缺陷; 构造方法的名字固定是类名, 有的类, 需要有多种不同的构造方法, 但是构造方法的名字有固定, 就只能使用方法重载的方式来实现了, 当时这里存在一定的局限性!在这里插入图片描述

    • 此时工厂模式就可以解决上述的问题了

      • 使用工厂模式, 不适用构造方法了, 使用普通的方法来构造对象, 这样的方法名就可以是任意的了
      • 普通方法内部, 在new 对象 – 由于普通方法的目的是为了创建对象来, 这样的方法一般都是静态的在这里插入图片描述

使用工程模式创建线程池

  • 使用 Executors.newFixedThreadPool(10) 能够创建出固定包含 10 个线程的线程池
  • 返回值类型为 ExecutorServer
  • 通过ExecutorServer.submit 可以注册一个任务到线程池中
 public static void main(String[] args) {
     ExecutorService pool = Executors.newFixedThreadPool(10);
     for (int i = 0; i < 1000; i++) {
         pool.submit(() -> {
             System.out.println("Hello thread Pool");
         });
     }
 }
  • Executors 创建线程池的几种方式
    • newFixedThreadPool: 创建固定线程数的线程池
    • newCachedThreadPool: 创建线程数目动态增长的线程池
    • newSingleThreadExecutor: 创建只包含当个线程的线程池
    • newScheduleThreadPool: 设定延迟时间后执行命令, 后定期执行命令, 是进阶版的 Timer
  • Executors 本质是ThreadPoolExecutor 类的封装

使用 Java原生的线程池构造方法来创建 (重点)

在这里插入图片描述

  • 参数含义:
    • int corePoolSize
      • 核心线程数
      • ThreadPoolExecutor 里面的线程个数, 并非是固定不变的, 会根据当前任务的情况动态发生变化(自适应)
      • 至少得有这些线程, 哪怕线程里面的人物一点也没有
    • int maximumPoolSize
      • 最大线程数
      • 最多不能超过这些线程, 哪怕线程池忙的冒烟了, 也不能比这个数目更多了

上述两个参数, 做到了既能保证繁忙的时候高效处理任务, 又能保证空间的时候不会浪费资源


  • long keepAliveTime, TimeUnit unit
    • 前者表示的是数值; 后者是一个枚举变量, 里面定义时间的各种单位

这个两个参数, 说明了, 多余的线程, 空间闲时间超过指定的时间阈值, 就可以被销毁了!


  • BlockingQueue<Runnable> workQueue
    • 线程池内部有很多任务, 这些任务可以使用阻塞队列来管理
    • 线程池可以内置阻塞队列, 也可以手动指定一个
  • ThreadFactory threadFactory
    • 工厂模式, 通过这个工厂类创建线程
  • RejectedExecutionHandler handler
    • 线程池考察的重点, 拒绝方式/拒绝策略
    • 线程池, 有一个阻塞队列, 当阻塞队列满了之后, 继续添加任务, 应该如何应对
    • 对应的处理动作如下
      • ThreadPoolExecutor.AbortPolicy
        • 直接抛出异常, 线程池直接不干活了
      • ThreadPoolExecutor.CallerRunsPolicy
        • 谁是添加这个新任务的线程, 谁就去执行这个任务
      • ThreadPoolExecutor.DiscardOldestPolicy
        • 丢弃最早的任务, 执行新的任务
      • ThreadPoolExecutor.DiscardPolicy
        • 直接把这个新的任务给丢弃了

创建线程池方法的总结

上述都是创建线程池的手段, 具体用什么方法创建线程池, 主要看的是具体的应用场景

线程池中线程数量的思考

  • 线程池中线程的数量主要是看线程工作的类型来决定的
  • 主要应用类型所对应的线程数量
    • “CPU密集型”
      • 此时线程的工作全是运算
      • 大部分工作都是在 CPU 上完成的, CPU 得给他安排核心去完成工作, 才可以有进展
      • 如果 CPU 是 N 个核心, 当你线程数量也是 N 的时候, 理想情况 每个 核心 上一个线程
      • 如果搞很多线程, 线程也是在排队等待, 不会有新的进展
    • “IO密集型”
      • 读写文件, 等待用户输入, 网络通信
        • 涉及到大量的等待事件, 等待的过程中没有使用 CPU
        • 这样的线程就算更多写, 也不会给CPU 造成太大的负担
        • 比如 CPU 是 16 个核心, 写 32 个线程 – 由于是 IO 密集的, 这里的大部分线程都在等, 都不消耗 CPU, 反而 CPU 的占用情况还很低
  • 实际开发中, 一个线程往往是一部分工作是 CPU 密集的, 一部分工作是 IO 密集的; 此时, 一个线程, 几成是在 CPU 上运行, 几成实在等待IO, 说不好; 这里更好的做法, 是通过实验的方法, 来找到合适的线程数!
  • 性能测试, 尝试不同的线程数目, 尝试过程中, 找打性能和系统资源开销比较均衡的数值

4.3 线程池模拟实现

public class MyThreadPool {
    BlockingDeque<Runnable> queue = new LinkedBlockingDeque<>(10);
    // 通过这个方法, 来把任务添加到线程池中.
    public void submit(Runnable runnable) throws InterruptedException {
        queue.put(runnable);
    }

    // n 表示线程池里有几个线程.
    // 创建了一个固定数量的线程池.
    public MyThreadPool(int n) {
        for (int i = 0; i < n; i++) {
            Thread t = new Thread(() -> {
               while (true) {
                   try {
                       // 取出任务, 并执行~~
                       Runnable runnable = queue.take();
                       runnable.run();
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
               }
            });
            t.start();
        }
    }
}

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

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

相关文章

JetBrains系列IDE全家桶激活

jetbrains全家桶 正版授权&#xff0c;这里有账号授权的渠道&#xff1a; https://www.mano100.cn/thread-1942-1-1.html 附加授权后的一张图片

YOLOv8改进实战 | 更换主干网络Backbone(二)之轻量化模型GhostnetV2

前言 轻量化网络设计是一种针对移动设备等资源受限环境的深度学习模型设计方法。下面是一些常见的轻量化网络设计方法: 网络剪枝:移除神经网络中冗余的连接和参数,以达到模型压缩和加速的目的。分组卷积:将卷积操作分解为若干个较小的卷积操作,并将它们分别作用于输入的不…

9 线程池

为什么要使用线程池&#xff1f; 1 重复利用已创建的线程&#xff0c;减少线程创建和销毁带来的开销 2 提高响应速度&#xff1a;任务可以不用等待线程创建就能立即执行&#xff08;T1 创建线程 T2执行任务 T3销毁线程&#xff09;&#xff0c;若T1T3>T2&#xff0c;可以通过…

macOS Tor 在启动期间退出

macos Tor 在启动期间退出。这可能是您的 torrc 文件存在错误&#xff0c;或者 Tor 或您的系统中的其他程序存在问题&#xff0c;或者硬件问题。在您解决此问题并重新启动 Tor 前&#xff0c;Tor 浏览器将不会启动。退出Tor、卸载Tor、删除目录 TorBrowser-Data、重启电脑 访…

关于antdpro的EdittableProTable编辑时两个下拉搜索框的数据联动以及数据回显(以及踩坑)

需求&#xff1a;使用antdpro的editprotable编辑两个下拉框&#xff0c;且下拉框是一个搜索下拉框。下拉框1和2的值是一个编码和名字的联动关系&#xff0c;1变化会带动2&#xff0c;2变化会带动1的一个联动作用。&#xff08;最后有略完整的代码&#xff0c;但是因为公司项目问…

059:mapboxGL监听键盘事件,通过eastTo控制左右旋转

第059个 点击查看专栏目录 本示例是介绍演示如何在vue+mapbox中监听键盘事件,通过eastTo控制左右旋转。 本例通过easeTo方法来加减一定数值的bearing角度,通过.addEventListener的方法来监听键盘的按键动作。这里一定要设置interactive: false, 否则展现不出来旋转效果。 直…

小微企业低成本获客攻略,媒介盒子无偿分享

品牌推广已经成为企业获客的主要手段&#xff0c;品牌推广能够使企业将自身的品牌、产品信息传递到受众面前&#xff0c;但是小微企业在公司成立之初往往资金有限&#xff0c;那么小微企业如何用低成本精准获客呢&#xff1f;可以试试软文。接下来媒介盒子就告诉大家&#xff0…

IO入门day1

课程分为三部分&#xff1a;IO文件-动态库、静态库制作&#xff0c;进程&#xff08;本地进程间通信&#xff09;&#xff0c;线程&#xff08;线程通信&#xff09; 特点&#xff1a;学习大量的函数接口调用-函数名及功能、man手册查看参数 一、IO介绍 1、概念 对文件的输入输…

Vue3中使用富文本编辑器

在vue3中我们可以使用wangeditor/editor、wangeditor/editor-for-vue来实现其功能 npm地址&#xff1a;https://www.npmjs.com/package/wangeditor/editor-for-vue/v/5.1.12?activeTabreadme 官网&#xff1a;Editor 1. 安装 pnpm add wangeditor/editor # 或者 npm inst…

ESRI ArcGIS Desktop 10.8.2图文安装教程及下载

ArcGIS 是由美国著名的地理信息系统公司 Esri 开发的一款地理信息系统软件&#xff0c;它是目前全球最流行的 GIS 软件之一。ArcGIS 提供了图形化用户界面和数据分析工具&#xff0c;可以帮助用户管理、分析和可视化各种空间数据。ArcGIS Desktop是一个完整的桌面GIS软件套件&a…

2023“龙芯杯”信创攻防赛 | 赛宁网安技术支持

2023年10月19日&#xff0c;为深入贯彻国家网络强国战略思想&#xff0c;宣传国家网络安全顶层设计&#xff0c;落实《网络安全法》《数据安全法》等法律法规。由大学生网络安全尖锋训练营主办&#xff0c;龙芯中科技术股份有限公司承办&#xff0c;山石网科通信技术股份有限公…

解决XXLJOB重复执行问题--Redis加锁+注解+AOP

基于Redis加锁注解AOP解决JOB重复执行问题 现象解决方案自定义注解定义AOP策略redis 加锁实践 现象 线上xxljob有时候会遇到同一个任务在调度的时候重复执行&#xff0c;如下图&#xff1a; 线上JOB服务运行了2个实例&#xff0c;有时候会重复调度到同一个实例&#xff0c;有…

【标准化封装 SOT系列 】 B SOT-223

SOT-223 pin 间距 2.3mm 名称pin 数厂家 body DE矩形 (mm)SOT-2234ADI – LTC196 . 6.3/6.71*3.3/3.71

基于广义正态分布优化的BP神经网络(分类应用) - 附代码

基于广义正态分布优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码 文章目录 基于广义正态分布优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码1.鸢尾花iris数据介绍2.数据集整理3.广义正态分布优化BP神经网络3.1 BP神经网络参数设置3.2 广义正态分布算…

接口测试 Jmeter 接口测试 —— 请求 Headers 与传参方式

一、 背景&#xff1a; 在使用 Jmeter 进行接口测试时&#xff0c;有些小伙伴不知道 Headers 和请求参数 (Parameters&#xff0c;Body Data) 的联系&#xff0c;本文主要讲 Content-Type 为 application/x-www-form-urlencoded 和 application/json 的场景。 1、使用 Parame…

Cesium Vue(七)— GEOJSON数据展示

1. GeoJSON GeoJSON 是一种用于对各种地理数据结构进行编码的格式。 简而言之&#xff0c;GeoJSON为你提供了一种简单的格式来表示简单的地理特征以及它们的非空间属性。 结构&#xff1a; {"type": "Feature","geometry": {"type"…

交叉熵(crossentropy)损失

1 神经网络分类问题 分类问题&#xff0c;通常分为二分类问题与多分类问题&#xff08;大于2类&#xff09;。 2 二分类问题 2.1 网络设计 神经网络的最后一层&#xff08;输出层&#xff09;&#xff0c;为一个神经元&#xff0c;使用激活函数sigmoid。 tf.keras.layers.…

如何测试WordPress能否成功发送邮件

如果你的WordPress网站没有连接到SMTP服务器&#xff0c;就发不了邮件。 修改管理员密码&#xff0c;发不了邮件。 用户提交表单&#xff0c;你收不到邮件提醒。 确定真的需要配置SMTP 有的服务器在一键安装WordPress的时候&#xff0c;已经帮你配置好了&#xff08;比如Sit…

windos默认输入法开机无法使用,需要手动执行ctfmon.exe

windos默认输入法开机无法使用&#xff0c;需要手动执行ctfmon.exe 系统&#xff1a;win10专业版 第一种方法 以管理员身份运行cmd 执行 sfc /scannow 1.右键点击此电脑&#xff0c;点击管理&#xff0c;打开如图所示服务选项卡&#xff0c;找到如图所示服务点击启动&#…

数据结构与算法(十):动态规划与贪心算法

参考引用 Hello 算法 Github&#xff1a;hello-algo 1. 动态规划算法 动态规划将一个问题分解为一系列更小的子问题&#xff0c;并通过存储子问题的解来避免重复计算&#xff0c;从而大幅提升时间效率 问题&#xff1a;给定一个共有 n 阶的楼梯&#xff0c;你每步可以上 1 阶或…