Java多线程——wait和notify方法作用,线程的状态

news/2024/7/27 8:42:22/文章来源:https://blog.csdn.net/Pireley/article/details/136448702

在这里插入图片描述

目录

  • 引出
  • wait和notify方法作用
  • 线程的状态
  • 创建线程有几种方式?
    • 方式1:继承Thread创建线程
    • 方式2:通过Runnable
    • 方式3:通过Callable创建线程
    • 方式4:通过线程池
      • 概述
      • ThreadPoolExecutor API
      • 代码实现
      • 源码分析
      • 工作原理:
        • 线程池的阻塞队列选择
        • 线程池已满又有新任务?
        • 拒绝策略
      • 如何优化线程池配置?
      • Executors
  • 总结

引出

Java多线程——wait和notify方法作用,线程的状态


wait和notify方法作用

waitnotifynotifyAll 通常在多线程间协作、同步和通信时使用,以确保线程之间按照预期顺序执行,避免死锁和竞态条件。

wait()使当前线程阻塞,前提是 必须先获得锁,一般配合synchronized 关键字使用,即,一般在synchronized 同步代码块里使用 wait()、notify/notifyAll() 方法。notify/notifyAll() 的执行唤醒沉睡的线程。

wait() 方法

  • wait 方法是定义在 Object 类中的,意味着每个对象都有这个方法。
  • 当一个线程调用某个对象的 wait 方法时,它会暂时释放该对象的锁,并进入等待状态,直到其他线程通过相同对象的 notifynotifyAll 方法唤醒它。
  • wait 方法通常在以下情况使用:当线程需要等待某个条件满足时,可以调用 wait 方法进入等待状态,直到其他线程修改了对象的状态,并调用了相同对象的 notifynotifyAll 方法来唤醒等待的线程。

notify() 方法

  • notify 方法也是定义在 Object 类中的,它用于唤醒等待在某个对象上的一个线程(任选一个)。
  • 当一个线程调用某个对象的 notify 方法时,它会通知正在等待在该对象上的某个线程,使其从等待状态转移到就绪状态。然而,哪个线程被唤醒是不确定的,这取决于 JVM 的调度策略。

notifyAll() 方法

  • notifyAll 方法也是定义在 Object 类中的,它用于唤醒等待在某个对象上的所有线程。
  • 当一个线程调用某个对象的 notifyAll 方法时,它会唤醒所有等待在该对象上的线程,使它们从等待状态转移到就绪状态。
public class App7 {public static void main(String[] args) throws ExecutionException, InterruptedException {App7 app = new App7();Thread t1 = new Thread(() -> {synchronized (app) {try {System.out.println(Thread.currentThread().getName()+":start");app.wait();System.out.println(Thread.currentThread().getName()+":end");} catch (InterruptedException e) {e.printStackTrace();}}}, "t1");Thread t2 = new Thread(() -> {synchronized (app) {System.out.println(Thread.currentThread().getName()+":nofify");app.notify();}}, "t2");t1.start();TimeUnit.SECONDS.sleep(1);t2.start();}
}
public final native void wait(long timeout)
public final native void notify();

native 后的函数的实现不是用java写的。使用native关键字说明这个方法是原生函数,也就是这个方法是用C/C++语言实现的,并且被编译成了DLL,由java去调用。这些函数的实现体在DLL中,JDK的源代码中并不包含,你应该是看不到的。对于不同的平台它们也是不同的。这也是java的底层机制,实际上java就是在不同的平台上调用不同的native方法实现对操作系统的访问的。

线程的状态

NEW : 尚未启动的线程处于此状态。
RUNNABLE : 在Java虚拟机中执行的线程处于此状态。
BLOCKED : 被阻塞等待监视器锁定的线程处于此状态。
WAITING : 正在等待另一个线程执行特定动作的线程处于此状态。
TIMED_WAITING: 正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。
TERMINATED: 已退出的线程处于此状态。

在这里插入图片描述

创建线程有几种方式?

方式1:继承Thread创建线程

public class MyThread extends Thread {@Overridepublic void run() {for (int i = 0; i < 20; i++) {System.out.println(Thread.currentThread().getName() + ":" + i);}}public static void main(String[] args) {MyThread t1 = new MyThread();t1.start();MyThread t2 = new MyThread();t2.start();}
}

方式2:通过Runnable

public class App2 {public static void main(String[] args) {new Thread(()->{for (int i = 0; i < 20; i++) {System.out.println("i = " + i);}}).start();}
}

方式3:通过Callable创建线程

一个可取消的异步计算。FutureTask提供了对Future的基本实现,可以调用方法去开始和取消一个计算,可以查询计算是否完成并且获取计算结果。只有当计算完成时才能获取到计算结果,一旦计算完成,计算将不能被重启或者被取消,除非调用runAndReset方法。

总的来说,如果你需要在线程任务执行完毕后获取返回结果,或者需要在任务中处理受检查异常,那么你应该使用 Callable 接口。如果你只需要执行一个简单的线程任务而不关心返回结果,那么使用 Runnable 接口更加合适。

package cn.test;
import java.util.concurrent.*;
public class App3 {public static void main(String[] args)  {//1、计算任务,实现Callable接口Callable<String> callable = ()->{int sum = 0;for (int i = 0; i < 20; i++) {sum += i;// 耗时操作Thread.sleep(100);}return "计算结果:" + sum;};//2、创建FutureTask,传入callable对象FutureTask<String> futureTask = new FutureTask<>(callable);//3、创建启动线程Thread thread = new Thread(futureTask);thread.start();try {String result = futureTask.get(1, TimeUnit.SECONDS);System.out.println("result = " + result);} catch (InterruptedException e) {e.printStackTrace();} catch (ExecutionException e) {e.printStackTrace();} catch (TimeoutException e) {e.printStackTrace();// 超时中断执行futureTask.cancel(true);System.out.println("超时中断执行");}}
}

方式4:通过线程池

概述

线程过多会带来额外的开销,频繁创建和销毁大量线程需要占用系统资源,消耗大量时间。其中包括创建销毁线程的开销、调度线程的开销等等,同时也降低了计算机的整体性能。线程池维护多个线程,等待监督管理者分配可并发执行的任务。这种做法,一方面避免了处理任务时创建销毁线程开销的代价,另一方面避免了线程数量膨胀导致的过分调度问题,保证了对内核的充分利用。

ThreadPoolExecutor API

  public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler) {

corePoolSize :核心池的大小,如果调用了prestartAllCoreThreads()或者prestartCoreThread()方法,会直接预先创建corePoolSize指定大小的线程,否则当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;这样做的好处是,如果任务量很小,那么甚至就不需要缓存任务,corePoolSize的线程就可以应对;

maximumPoolSize:线程池最大线程数,表示在线程池中最多能创建多少个线程,如果运行中的线程超过了这个数字,那么相当于线程池已满,新来的任务会使用RejectedExecutionHandler 进行处理;

keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止,然后线程池的数目维持在corePoolSize 大小;

unit:参数keepAliveTime的时间单位;

workQueue:一个阻塞队列,用来存储等待执行的任务,如果当前对线程的需求超过了corePoolSize大小,才会放在这里;

threadFactory:线程工厂,主要用来创建线程,比如可以指定线程的名字;

handler:如果线程池已满,新的任务的处理方式

代码实现

public class App4 {// 线程池的核心线程数private static final int CORE_POOL_SIZE = 5;// 线程池的最大线程数private static final int MAX_POOL_SIZE = 10;// 当线程数大于核心线程数时,多余的空闲线程存活的最长时间private static final int KEEP_ALLOW_TIME = 100;// 任务队列大小,用来存储等待执行任务的队列private static final int QUEUE_CAPACITY = 100;public static void main(String[] args) {// handler 指定拒绝策略,当提交的任务过多不能及时处理,我们通过定制的策略处理任务ThreadPoolExecutor executor = new ThreadPoolExecutor(CORE_POOL_SIZE,MAX_POOL_SIZE,KEEP_ALLOW_TIME,TimeUnit.SECONDS,new ArrayBlockingQueue<>(QUEUE_CAPACITY),new ThreadPoolExecutor.CallerRunsPolicy());//executor.prestartAllCoreThreads();for (int i = 0; i < 10; i++) {Runnable runnable = () -> {System.out.println(Thread.currentThread().getName() + ":start");try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + ":end");};// 运行线程executor.execute(runnable);}// 终止线程池executor.shutdown();while (!executor.isTerminated()) {}System.out.println("Finish All");}
}

源码分析

/*The main pool control state, ctl, is an atomic integer packing* two conceptual fields*   workerCount, indicating the effective number of threads*   runState,    indicating whether running, shutting down etc   */
// 存放线程池的线程池内有效线程的数量 (workerCount)和运行状态 (runState) private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));private static int workerCountOf(int c) {return c & CAPACITY;}private final BlockingQueue<Runnable> workQueue;public void execute(Runnable command) {// 如果任务为null,则抛出异常。if (command == null)throw new NullPointerException();// ctl 中保存的线程池当前的一些状态信息int c = ctl.get();//  下面会涉及到 3 步 操作// 1.首先判断当前线程池中之行的任务数量是否小于 corePoolSize// 如果小于的话,通过addWorker(command, true)新建一个线程,并将任务(command)添加到该线程中;然后,启动该线程从而执行任务。if (workerCountOf(c) < corePoolSize) {if (addWorker(command, true))return;c = ctl.get();}// 2.如果当前之行的任务数量大于等于 corePoolSize 的时候就会走到这里// 通过 isRunning 方法判断线程池状态,线程池处于 RUNNING 状态才会被并且队列可以加入任务,该任务才会被加入进去if (isRunning(c) && workQueue.offer(command)) {int recheck = ctl.get();// 再次获取线程池状态,如果线程池状态不是 RUNNING 状态就需要从任务队列中移除任务,并尝试判断线程是否全部执行完毕。同时执行拒绝策略。if (!isRunning(recheck) && remove(command))reject(command);// 如果当前线程池为空就新创建一个线程并执行。else if (workerCountOf(recheck) == 0)addWorker(null, false);}//3. 通过addWorker(command, false)新建一个线程,并将任务(command)添加到该线程中;然后,启动该线程从而执行任务。//如果addWorker(command, false)执行失败,则通过reject()执行相应的拒绝策略的内容。else if (!addWorker(command, false))reject(command);}

工作原理:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 线程池刚创建时,里面没有一个线程。任务队列是作为参数传进来的。不过,就算队列里面有任务,线程池也不会马上执行它们。
  2. 当调用 execute() 方法添加一个任务时,线程池会做如下判断:
    • 如果正在运行的线程数量小于 corePoolSize,那么马上创建线程运行这个任务;
    • 如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入队列。
    • 如果这时候队列满了,而且正在运行的线程数量小于 maximumPoolSize,那么还是要创建线程运行这个任务;
    • 如果队列满了,而且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会抛出异常,告诉调用者“我不能再接受任务了”。

3,当一个线程完成任务时,它会从队列中取下一个任务来执行。
4,当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运行的线程数大于 corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到 corePoolSize 的大小。

这样的过程说明,并不是先加入任务就一定会先执行。假设队列大小为 10,corePoolSize 为 3,maximumPoolSize 为 6,那么当加入 20 个任务时,执行的顺序就是这样的:首先执行任务 1、2、3,然后任务 4~13 被放入队列。这时候队列满了,任务 14、15、16 会被马上执行,而任务 17~20 则会抛出异常。最终顺序是:1、2、3、14、15、16、4、5、6、7、8、9、10、11、12、13。

线程池的阻塞队列选择

如果线程数超过了corePoolSize,则开始把线程先放到阻塞队列里,相当于生产者消费者的一个数据通道,有以下一些阻塞队列可供选择:

  1. ArrayBlockingQueue

    ArrayBlockingQueue是一个有边界的阻塞队列,它的内部实现是一个数组。有边界的意思是它的容量是有限的,我们必须在其初始化的时候指定它的容量大小,容量大小一旦指定就不可改变。

  2. DelayQueue

    DelayQueue阻塞的是其内部元素,DelayQueue中的元素必须实现 java.util.concurrent.Delayed接口,该接口只有一个方法就是long getDelay(TimeUnit unit),返回值就是队列元素被释放前的保持时间,如果返回0或者一个负值,就意味着该元素已经到期需要被释放,此时DelayedQueue会通过其take()方法释放此对象,DelayQueue可应用于定时关闭连接、缓存对象,超时处理等各种场景;

  3. LinkedBlockingQueue

    LinkedBlockingQueue阻塞队列大小的配置是可选的,如果我们初始化时指定一个大小,它就是有边界的,如果不指定,它就是无边界的。说是无边界,其实是采用了默认大小为Integer.MAX_VALUE的容量 。它的内部实现是一个链表。

  4. PriorityBlockingQueue

    PriorityBlockingQueue是一个没有边界的队列,它的排序规则和 java.util.PriorityQueue一样。需要注意,PriorityBlockingQueue中允许插入null对象。所有插入PriorityBlockingQueue的对象必须实现 java.lang.Comparable接口,队列优先级的排序规则就是按照我们对这个接口的实现来定义的。

  5. SynchronousQueue

    SynchronousQueue队列内部仅允许容纳一个元素。当一个线程插入一个元素后会被阻塞,除非这个元素被另一个线程消费。

使用的最多的应该是LinkedBlockingQueue,注意一般情况下要配置一下队列大小,设置成有界队列,否则JVM内存会被撑爆!

线程池已满又有新任务?

如果线程池已经满了可是还有新的任务提交怎么办?

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

线程池已满的定义,是指运行线程数==maximumPoolSize,并且workQueue是有界队列并且已满(如果是无界队列当然永远不会满);

这时候再提交任务怎么办呢?线程池会将任务传递给最后一个参数RejectedExecutionHandler来处理,比如打印报错日志、抛出异常、存储到Mysql/redis用于后续处理等等,线程池默认也提供了几种处理方式,详见下一章:

拒绝策略

拒绝策略指的就是线程池已满情况下任务的处理策略,默认有以下几种:

1、ThreadPoolExecutor.AbortPolicy 中,处理程序遭到拒绝将抛出运行时RejectedExecutionException。

    /*** A handler for rejected tasks that throws a* {@code RejectedExecutionException}.*/public static class AbortPolicy implements RejectedExecutionHandler {/*** Creates an {@code AbortPolicy}.*/public AbortPolicy() { }/*** Always throws RejectedExecutionException.** @param r the runnable task requested to be executed* @param e the executor attempting to execute this task* @throws RejectedExecutionException always*/public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {throw new RejectedExecutionException("Task " + r.toString() +" rejected from " +e.toString());}}

2、在 ThreadPoolExecutor.CallerRunsPolicy,交给线程池调用所在的线程进行处理。

   /*** A handler for rejected tasks that runs the rejected task* directly in the calling thread of the {@code execute} method,* unless the executor has been shut down, in which case the task* is discarded.*/public static class CallerRunsPolicy implements RejectedExecutionHandler {/*** Creates a {@code CallerRunsPolicy}.*/public CallerRunsPolicy() { }/*** Executes task r in the caller's thread, unless the executor* has been shut down, in which case the task is discarded.** @param r the runnable task requested to be executed* @param e the executor attempting to execute this task*/public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {if (!e.isShutdown()) {r.run();}}}

3、在 ThreadPoolExecutor.DiscardPolicy 中,直接丢弃后来的任务

 /*** A handler for rejected tasks that silently discards the* rejected task.*/public static class DiscardPolicy implements RejectedExecutionHandler {/*** Creates a {@code DiscardPolicy}.*/public DiscardPolicy() { }/*** Does nothing, which has the effect of discarding task r.** @param r the runnable task requested to be executed* @param e the executor attempting to execute this task*/public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {}}

4、在 ThreadPoolExecutor.DiscardOldestPolicy 丢弃队列里最老的任务,将当前这个任务继续提交给线程池。

 /*** A handler for rejected tasks that discards the oldest unhandled* request and then retries {@code execute}, unless the executor* is shut down, in which case the task is discarded.*/public static class DiscardOldestPolicy implements RejectedExecutionHandler {/*** Creates a {@code DiscardOldestPolicy} for the given executor.*/public DiscardOldestPolicy() { }/*** Obtains and ignores the next task that the executor* would otherwise execute, if one is immediately available,* and then retries execution of task r, unless the executor* is shut down, in which case task r is instead discarded.** @param r the runnable task requested to be executed* @param e the executor attempting to execute this task*/public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {if (!e.isShutdown()) {e.getQueue().poll();e.execute(r);}}}

5、当然也可以自己实现处理策略类,继承RejectedExecutionHandler接口即可,该接口只有一个方法:
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);

如何优化线程池配置?

如何合理配置线程池大小,仅供参考。

一般需要根据任务的类型来配置线程池大小:

如果是CPU密集型任务,就需要尽量压榨CPU,参考值可以设为【(CPU总核数)】 或者 【(CPU总核数+1)】

如果是IO密集型任务,类似 网络I/O、数据库、磁盘I/O 等,参考值可以设置为【(2 * CPU总核数)】

当然,这只是一个参考值,具体的设置还需要根据实际情况进行调整,比如可以先将线程池大小设置为参考值,

再观察任务运行情况和系统负载、资源利用率来进行适当调整。

其中NCPU的指的是CPU的核心数,可以使用下面方式来获取;

   public static void main(String[] args) {int ncpu = Runtime.getRuntime().availableProcessors();System.out.println("cpu核数 = " + ncpu);}

Executors

通过Executors类提供四种线程池。创建方法为静态方式创建。

Executors.newFixedThreadPool();

返回线程池对象。创建的是有界线程池,也就是池中的线程个数可以指定最大数量。

    public static ExecutorService newFixedThreadPool(int nThreads) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());}

可见该方法让keepAliveTime为0,即限制了线程数必须小于等于corePoolSize。而多出的线程则会被无界队列所存储,在其中排队。

Executors.newCachedThreadPool();

创建一个可缓存线程池,线程池长度超过处理需要时,可灵活回收空闲线程,若无可回收线程则新建线程。

   public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());}

该方法中所有线程均由SynchronousQueue管理,且不设置线程数量上限。对于SynchronousQueue,每个插入线程必须等待另一线程的对应移除操作。(即该队列没有容量,仅试图取得元素时元素才存在)因而,该方法实现了,如果有线程空闲,则使用空闲线程进行操作,否则就会创建新线程。

Executors.newScheduledThreadPool();

创建一个定长线程池,相对于FixedThreadPool,它支持周期性执行和延期执行。

1、延迟3秒执行

public static void main(String[] args) {ScheduledExecutorService executorService = Executors.newScheduledThreadPool(2);executorService.schedule(()->{System.out.println(Thread.currentThread().getName()+":线程启动");},3, TimeUnit.SECONDS);executorService.shutdown();
}

2、每三秒隔一秒执行

public static void main(String[] args) {ScheduledExecutorService executorService = Executors.newScheduledThreadPool(2);executorService.scheduleAtFixedRate(()->{System.out.println(Thread.currentThread().getName()+":线程启动");},1,3, TimeUnit.SECONDS);
}

Executors.newSingleThreadExecutor();

创建一个单线程线程池,只会用唯一的工作线程执行任务,保证所有任务按FIFO,LIFO的优先级执行。

在实现上,其相当于一个线程数为1的FixedThreadPool

    public static ExecutorService newSingleThreadExecutor() {return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));}

总结

Java多线程——wait和notify方法作用,线程的状态

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

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

相关文章

力扣刷题Days7第二题--242.有效的字母异位词(js)

目录 1&#xff0c;题目 2&#xff0c;代码 2.1 我思考完成的-初版--哈希表思想 2.2略改进 2.3排序思想 3&#xff0c;学习与总结 3.1 判断数组元素是否都为0 3.2总结 1&#xff0c;题目 给定两个字符串 s 和 t &#xff0c;编写一个函数来判断 t 是否是 s 的字母异位词…

okHttp MediaType MIME格式详解

一、介绍 我们在做数据上传时&#xff0c;经常会用到Okhttp的开源库&#xff0c;okhttp开源库也遵循html提交的MIME数据格式。 所以我们经常会看到applicaiton/json这样的格式在传。 但是如果涉及到其他文件等就需要详细的数据格式&#xff0c;否则服务端无法解析 二、okHt…

重学SpringBoot3-@EnableConfigurationProperties注解

重学SpringBoot3-EnableConfigurationProperties注解 1. 引言2. EnableConfigurationProperties 的作用3. 使用示例4. 总结 1. 引言 Spring Boot 提供了一种便捷的方式来管理和校验应用程序的配置&#xff0c;即通过类型安全的配置属性。EnableConfigurationProperties 注解在…

光谱整形1

华为张德江&#xff1a;下一代光传送网将走向400G80波WDM系统_通信世界网 (cww.net.cn) 张德江指出&#xff0c;400G WDM系统具有三大基本特征&#xff1a;支持400G80波&#xff0c;单纤32T超大容量&#xff0c;传输距离与100G相当&#xff1b;支持32维以上的光交叉&#xff1…

搜维尔科技:3D Systems Geomagic Design X 逆向工程软件

产品概述 3D Systems Geomagic Design X 是全面的逆向工程软件 GeomagicoDesign XTM是全面的逆向工程软件&#xff0c;它结合了基于特征的CAD数模与三维扫描数据处理&#xff0c;使您能创建出可编辑、基于特征的CAD数模&#xff0c;并与您现有的CAD软件兼容。 拓展您的设计能…

请说明Vue中的异步组件加载

Vue中的异步组件加载是指当页面需要渲染某个组件时&#xff0c;可以在需要时再去加载这个组件&#xff0c;而不是在页面初始化的时候就将所有组件一次性加载进来。这种方式能够有效降低页面的初始加载时间&#xff0c;提升用户体验。 在Vue中&#xff0c;我们可以使用import函…

springboot实现多线程开发(使用@Async注解,简单易上手)

根据springboot的核心思想便捷开发&#xff0c;使用多线程也变得简单起来&#xff0c;通过一下几个步骤即可实现。 核心注解 EnableAsync将此注解加在启动类上&#xff0c;使项目支持多线程。 Async 使用我们的Async注解在所需要进行多线程的类上即可实现。 配置线程池 …

从第一原理看大语言模型

大模型基础框架 大模型幻觉问题 大模型能力 思维链模式 思维链模式激发的是大模型的推理能力 LLM知识能力RAG

【设计模式】观察者模式及函数式编程的替代C++

本文介绍观察者模式以及使用函数式编程替代简单的策略模式。 观察者模式 观察者模式是一种行为型设计模式&#xff0c;它定义了一种一对多的依赖关系&#xff0c;当一个对象的状态发生改变时&#xff0c;其所有依赖者都会收到通知并自动更新。 当对象间存在一对多关系时&#…

day13_微服务监控Nginx(微服务集成SBA)

文章目录 1 微服务系统监控1.1 监控系统的意义1.2 SBA监控方案1.3 SBA实战1.3.1 创建SBA服务端1.3.2 微服务集成SBA 1.4 微服务集成logback1.5 配置邮件告警 2 Nginx2.1 Nginx简介2.2 下载和安装2.2.1 方式1&#xff1a;window本地安装2.2.1.1 下载2.2.1.2 安装2.2.1.3 目录结构…

【开源物联网平台】FastBee认证方式和MQTT主题设计

&#x1f308; 个人主页&#xff1a;帐篷Li &#x1f525; 系列专栏&#xff1a;FastBee物联网开源项目 &#x1f4aa;&#x1f3fb; 专注于简单&#xff0c;易用&#xff0c;可拓展&#xff0c;低成本商业化的AIOT物联网解决方案 目录 一、接入步骤 1.1 设备认证 1.2 设备交…

新项目,Linux上一键安装MySQL,Redis,Nacos,Minio

大家好&#xff0c;我是 jonssonyan 分享一个我的一个开源项目&#xff0c;这是一个在 Linux 平台上一键安装各种软件的脚本项目&#xff0c;脚本使用 Shell 语言编写&#xff0c;后续还会增加更多软件的一键安装&#xff0c;代码在 GitHub 上全部开源的&#xff0c;开源地址如…

TypeScript(三)对象,接口,类,泛型

一、 对象 Typescript 中 Object 类型不单是指普通对象类型&#xff0c;它泛指所有的非原始类型&#xff0c;也就是对象&#xff0c;数组还有函数。 普通对象就是键值对的集合&#xff0c;我们可以使用接口来定义对象的结构。interface Person { // Person是接口CHname: strin…

Python算法题集_搜索插入位置

Python算法题集_搜索插入位置 题51&#xff1a;搜索插入位置1. 示例说明2. 题目解析- 题意分解- 优化思路- 测量工具 3. 代码展开1) 标准求解【二分法查找】2) 改进版一【二分法查找终止条件判断】3) 改进版二【第三方模块】 4. 最优算法5. 相关资源 本文为Python算法题集之一的…

基于springboot的智能物流管理系统论文

智能物流管理系统 摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术上已逐步成熟。本文介绍了智能物流管理系统的开发全过程。通过分析智能物流管理系统管理的不足&#xff0c;创建了一个计算机管理智能物流管理系统的方案。文章介绍了智…

OpenCV与AI深度学习 | 基于OpenCV实现模糊检测 / 自动对焦

本文来源公众号“OpenCV与AI深度学习”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;基于OpenCV实现模糊检测 / 自动对焦 导 读 本文主要介绍使用OpenCV实现图像模糊检测/相机自动对焦功能。 前 言 为了检测图片是否对焦&…

深入浅出(二)MVVM

MVVM 1. 简介2. 示例 1. 简介 2. 示例 示例下载地址&#xff1a;https://download.csdn.net/download/qq_43572400/88925141 创建C# WPF应用(.NET Framework)工程&#xff0c;WpfApp1 添加程序集 GalaSoft.MvvmLight 创建ViewModel文件夹&#xff0c;并创建MainWindowV…

抖音视频评论批量采集软件|视频下载工具

《轻松搞定&#xff01;视频评论批量采集软件&#xff0c;助您高效工作》 在短视频这个充满活力和创意的平台上&#xff0c;了解用户评论是了解市场和观众心声的重要途径之一。为了帮助您快速获取大量视频评论数据&#xff0c;我们推出了一款操作便捷、功能强大的软件&#xff…

18个惊艳的可视化大屏(第20辑):物联网场景

实时监控和管理 物联网系统通常涉及大量的传感器、设备和数据&#xff0c;通过将这些数据可视化展示在大屏上&#xff0c;可以实时监控和管理物联网系统的运行状态。这有助于及时发现问题、快速响应&#xff0c;并提高系统的可靠性和稳定性。 数据分析和决策支持 可视化大屏可…

Redis小白入门教程

Redis入门教程 1. Redis入门1.1 Redis简介1.2 Redis服务启动与停止1.2.1 Redis下载1.2.2 服务启动命令1.2.3 客户端连接命令1.2.4 修改Redis配置文件 2. Redis数据类型2.1 五种常用数据类型介绍2.1.1 字符串操作命令2.1.2 哈希操作命令2.1.3 列表操作命令2.1.4 集合操作命令2.1…