Java多线程Thread及其原理深度解析

文章目录

  • 1. 实现多线程的方式
  • 2. Thread 部分源码
    • 2.1. native 方法注册
    • 2.2. Thread 中的成员变量
    • 2.3. Thread 构造方法与初始化
    • 2.4. Thread 线程状态与操作系统状态
    • 2.4. start() 与 run() 方法
    • 2.5. sleep() 方法
    • 2.6. join() 方法
    • 2.7. interrupt() 方法

本文参考:

线程的创建 — Thread与Runnable详解

[聊一聊多线程的 run() 和 start(),挖一挖start0 ]

【高并发】Thread类的源码精髓

【Java】Thread类中的join()方法原理

Java 线程状态之 BLOCKED

java线程状态与操作系统线程状态的关系

1. 实现多线程的方式

package com.jxz.threads;import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;/*** @Author jiangxuzhao* @Description* @Date 2024/9/10*/
@Slf4j
public class ThreadCreateTest {@Test@SneakyThrowspublic void test1() {// 匿名类重写 Thread#runThread thread1 = new Thread() {@Overridepublic void run() {log.info("extend thread#run...");}};thread1.start();// 避免主线程直接结束Thread.sleep(1000);}@Test@SneakyThrowspublic void test2() {// Lambda 表达式定义实现 Runnable target, Thread#run 方法最终调用 target#runThread thread2 = new Thread(() -> {log.info("implement 自定义变量 target 的 Runnable#run...");});thread2.start();// 避免主线程直接结束Thread.sleep(1000);}@Test@SneakyThrowspublic void test3() {// Lambda 表达式定义实现 callable#call,可以在主线程中通过 Future#get 阻塞获取结果 resultFutureTask<String> stringFutureTask = new FutureTask<>(() -> {log.info("implement Callable#call");return Thread.currentThread().getName();});Thread thread3 = new Thread(stringFutureTask);thread3.start();// 阻塞获取结果,不用担心主线程直接结束log.info("thread3 futureTask callable output = {}", stringFutureTask.get());}@Test@SneakyThrowspublic void test4() {// 线程池实现异步多线程ExecutorService executorService = Executors.newFixedThreadPool(1);Future<String> stringFuture = executorService.submit(() -> {log.info("thread pool submit");return Thread.currentThread().getName();});// 阻塞获取结果,不用担心主线程直接结束log.info("thead submit output = {}", stringFuture.get());}
}

2. Thread 部分源码

2.1. native 方法注册

public
class Thread implements Runnable {// 在 jdk 底层的 Thread.c 文件中定义了各种方法private static native void registerNatives();// 确保 registerNatives 是 <clinit> 中第一件做的事static {registerNatives();}
}

Thread#registerNatives 作为本地方法,主要作用是注册一些本地方法供 Thread 类使用,如 start0(), stop0() 等。

该方法被放在一个本地静态代码块中,并且该代码块被放在类中最靠前的位置,确保当 Thread 类被加载到 JVM 中时,调用 第一时间就会注册所有的本地方法。

所有的本地方法都是定义在 JDK 源码的 Thread.c 文件中的,它定义了各个操作系统平台都要用到的关于线程的基本操作。

可以专门去下载 openjdk 1.8 的源码一探究竟:

在这里插入图片描述

或者直接阅读 openjdk8 在线的源码:

https://hg.openjdk.org/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/native/java/lang/Thread.c

2.2. Thread 中的成员变量

针对其中常见的几个变量做了中文注释

// 当前线程的名称
private volatile String name;
private int            priority;
private Thread         threadQ;
private long           eetop;/* Whether or not to single_step this thread. */
private boolean     single_step;// 当前线程是否在后台运行
/* Whether or not the thread is a daemon thread. */
private boolean     daemon = false;/* JVM state */
private boolean     stillborn = false;// init 构造方法中传入的执行任务,当其不为空时,会执行此任务
/* What will be run. */
private Runnable target;// 当前线程所在的线程组
/* The group of this thread */
private ThreadGroup group;// 当前线程的类加载器
/* The context ClassLoader for this thread */
private ClassLoader contextClassLoader;/* The inherited AccessControlContext of this thread */
private AccessControlContext inheritedAccessControlContext;// 被用来定义 "Thread-" + nextThreadNum() 的线程名,自增的序号在线程池打印日志中很常见
// 静态变量 threadInitNumber 在 static synchronized 方法中自增,这个方法被调用时在 Thread.class 类上加 synchronized 锁,保证单台 JVM 虚拟机上都通过 Thread.class 并发创建线程 init 时,线程自增序号的并发安全
/* For autonumbering anonymous threads. */
private static int threadInitNumber;
private static synchronized int nextThreadNum() {return threadInitNumber++;
}// 每个线程都维护一个 ThreadLocalMap,这个在保障线程安全的 ThreadLocal 中经常出现
/* ThreadLocal values pertaining to this thread. This map is maintained* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;/** InheritableThreadLocal values pertaining to this thread. This map is* maintained by the InheritableThreadLocal class.*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;/** The requested stack size for this thread, or 0 if the creator did* not specify a stack size.  It is up to the VM to do whatever it* likes with this number; some VMs will ignore it.*/
private long stackSize;/** JVM-private state that persists after native thread termination.*/
private long nativeParkEventPointer;/** Thread ID*/
private long tid;/* For generating thread ID */
private static long threadSeqNumber;/* Java thread status for tools,* initialized to indicate thread 'not yet started'*/private volatile int threadStatus = 0;private static synchronized long nextThreadID() {return ++threadSeqNumber;
}/*** The argument supplied to the current call to* java.util.concurrent.locks.LockSupport.park.* Set by (private) java.util.concurrent.locks.LockSupport.setBlocker* Accessed using java.util.concurrent.locks.LockSupport.getBlocker*/
volatile Object parkBlocker;/* The object in which this thread is blocked in an interruptible I/O* operation, if any.  The blocker's interrupt method should be invoked* after setting this thread's interrupt status.*/
private volatile Interruptible blocker;
private final Object blockerLock = new Object();/* Set the blocker field; invoked via sun.misc.SharedSecrets from java.nio code*/
void blockedOn(Interruptible b) {synchronized (blockerLock) {blocker = b;}
}/*** The minimum priority that a thread can have.*/
public final static int MIN_PRIORITY = 1;/*** The default priority that is assigned to a thread.*/
public final static int NORM_PRIORITY = 5;/*** The maximum priority that a thread can have.*/
public final static int MAX_PRIORITY = 10;

2.3. Thread 构造方法与初始化

构造方法:

Thread 具有多个重载的构造函数,内部都是调用 Thread#init() 方法初始化,我们常用的就是传入 Thread(Runnable target) 以及 Thread(Runnable target, String name)

public Thread() {init(null, null, "Thread-" + nextThreadNum(), 0);
}public Thread(Runnable target) {init(null, target, "Thread-" + nextThreadNum(), 0);
}Thread(Runnable target, AccessControlContext acc) {init(null, target, "Thread-" + nextThreadNum(), 0, acc, false);
}public Thread(ThreadGroup group, Runnable target) {init(group, target, "Thread-" + nextThreadNum(), 0);
}public Thread(String name) {init(null, null, name, 0);
}public Thread(ThreadGroup group, String name) {init(group, null, name, 0);
}public Thread(Runnable target, String name) {init(null, target, name, 0);
}public Thread(ThreadGroup group, Runnable target, String name) {init(group, target, name, 0);
}public Thread(ThreadGroup group, Runnable target, String name,long stackSize) {init(group, target, name, stackSize);
}

init 初始化方法:

主要完成成员变量赋值的操作,包括 Runnable target 变量的赋值。后面可以看到,如果在构造器中就传入这个 Runnable,Thread#run 就会执行这个 Runnable.

private void init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc,boolean inheritThreadLocals) {if (name == null) {throw new NullPointerException("name cannot be null");}this.name = name;Thread parent = currentThread();SecurityManager security = System.getSecurityManager();if (g == null) {/* Determine if it's an applet or not *//* If there is a security manager, ask the security managerwhat to do. */if (security != null) {g = security.getThreadGroup();}/* If the security doesn't have a strong opinion of the matteruse the parent thread group. */if (g == null) {g = parent.getThreadGroup();}}/* checkAccess regardless of whether or not threadgroup isexplicitly passed in. */g.checkAccess();/** Do we have the required permissions?*/if (security != null) {if (isCCLOverridden(getClass())) {security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);}}g.addUnstarted();this.group = g;this.daemon = parent.isDaemon();this.priority = parent.getPriority();if (security == null || isCCLOverridden(parent.getClass()))this.contextClassLoader = parent.getContextClassLoader();elsethis.contextClassLoader = parent.contextClassLoader;this.inheritedAccessControlContext =acc != null ? acc : AccessController.getContext();// 就是上面成员变量中的 target,在这里赋值this.target = target;setPriority(priority);if (inheritThreadLocals && parent.inheritableThreadLocals != null)this.inheritableThreadLocals =ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);/* Stash the specified stack size in case the VM cares */this.stackSize = stackSize;/* Set thread ID */tid = nextThreadID();
}

2.4. Thread 线程状态与操作系统状态

public enum State {/*** Thread state for a thread which has not yet started.*/// 初始化状态NEW,/*** Thread state for a runnable thread.  A thread in the runnable* state is executing in the Java virtual machine but it may* be waiting for other resources from the operating system* such as processor.*/// 可运行状态,可运行状态可以包括:运行中状态和就绪状态。RUNNABLE,/*** Thread state for a thread blocked waiting for a monitor lock.* A thread in the blocked state is waiting for a monitor lock* to enter a synchronized block/method or* reenter a synchronized block/method after calling* {@link Object#wait() Object.wait}.*/// 线程阻塞状态BLOCKED,/*** Thread state for a waiting thread.* A thread is in the waiting state due to calling one of the* following methods:* <ul>*   <li>{@link Object#wait() Object.wait} with no timeout</li>*   <li>{@link #join() Thread.join} with no timeout</li>*   <li>{@link LockSupport#park() LockSupport.park}</li>* </ul>** <p>A thread in the waiting state is waiting for another thread to* perform a particular action.** For example, a thread that has called <tt>Object.wait()</tt>* on an object is waiting for another thread to call* <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on* that object. A thread that has called <tt>Thread.join()</tt>* is waiting for a specified thread to terminate.*/// 等待状态WAITING,/*** Thread state for a waiting thread with a specified waiting time.* A thread is in the timed waiting state due to calling one of* the following methods with a specified positive waiting time:* <ul>*   <li>{@link #sleep Thread.sleep}</li>*   <li>{@link Object#wait(long) Object.wait} with timeout</li>*   <li>{@link #join(long) Thread.join} with timeout</li>*   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>*   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>* </ul>*/// 超时等待状态TIMED_WAITING,/*** Thread state for a terminated thread.* The thread has completed execution.*/// 线程终止状态TERMINATED;
}
  • NEW: 初始状态,线程被构建,但是还没有调用 Thread#start() 方法

  • RUNNABLE: 可运行状态,包括运行中和就绪状态。从源码的注释中可以看出来,就绪状态就是线程在 JVM 中有资格运行,但是由于操作系统调度的原因尚未执行,可能线程在等待操作系统释放资源,比方说处理器资源。

  • BLOCKED: 阻塞状态,处于这个状态的线程等待别的线程释放 monitor 锁以进入 synchronized 块;或者调用 Object#wait() 方法释放锁进入等待队列后(此时是 WAITING 状态),被其他线程 notify() 唤醒时不能立刻从上次 wait 的地方恢复执行,再次进入 synchronized 块还需要和别的线程竞争锁。
    总结来说,线程因为获取不到锁而无法进入同步代码块时,处于 BLOCKED 阻塞状态。

    一篇用例子解释的文章参考:Java 线程状态之 BLOCKED

  • WAITING: 等待状态,处于该状态的线程需要其他线程对其进行通知或者中断等操作,从而进入下一个状态。

  • TIMED_WAITING: 超时等待状态,相比于 WAITING 状态持续等待,该状态可以在一定时间后自行返回

  • TERMINATED: 终止状态,当前线程执行完毕

下面就用一张图表示了Java线程各种状态的流转,其中夹杂着操作系统线程的状态定义,其中标红的部分表示 Java 状态

在这里插入图片描述

对比操作系统线程状态,包括 new、terminated、ready、running、waiting,除去初始化 new 和 terminated 终止状态,一个线程运行中的状态只有:

  • ready: 线程已创建,等待系统调度分配 CPU 资源
  • running: 线程获得了 CPU 使用权,正在运算
  • waiting: 线程等待(或者说挂起),让出 CPU 资源给其他线程使用

其对应关系我理解如下:

其中 Java 线程状态 RUNNABLE 包括操作系统状态的运行 running 和就绪 ready,操作系统的 waiting 包含了 BLOCKED 阻塞挂起状态。

在这里插入图片描述

2.4. start() 与 run() 方法

新线程构造之后,只有调用 start() 才能让 JVM 创建线程并进入运行状态,Thread#start() 源码如下,主要包含几大步骤:

  1. 判断线程状态是否为 NEW 初始化

  2. 加入线程组

  3. 调用 native 方法 start0() 通知底层 JVM 启动一个线程,start0() 就是前面 registerNatives() 本地方法注册的一个启动方法

  4. 如果启动失败,把线程从线程组中删除

public synchronized void start() {/*** This method is not invoked for the main method thread or "system"* group threads created/set up by the VM. Any new functionality added* to this method in the future may have to also be added to the VM.** A zero status value corresponds to state "NEW".*/// 1. 判断线程状态是否为 NEW 初始化,否则直接抛出异常if (threadStatus != 0)throw new IllegalThreadStateException();/* Notify the group that this thread is about to be started* so that it can be added to the group's list of threads* and the group's unstarted count can be decremented. */// 2. 加入线程组group.add(this);// 线程是否已经启动标志位,启动后设置为 trueboolean started = false;try {// 3. 调用本地方法启动线程start0();// 启动后设置标志位为 truestarted = true;} finally {try {// 4. 如果启动失败,把线程从线程组中移除if (!started) {group.threadStartFailed(this);}} catch (Throwable ignore) {/* do nothing. If start0 threw a Throwable thenit will be passed up the call stack */}}
}// JVM 真正启动线程的本地方法
private native void start0();

从 start() 源码中可以看出来以下几点:

  1. start() 加上 synchronized 关键词,单个 Thread 实例在这个 JVM 进程中运行是同步的,因此不会出现并发问题。同步检查该线程的状态,如果不是初始化状态则抛出异常。
  2. start() 方法并没有直接调用我们定义的 run() 方法,是因为 Thread#start() 底层调用 Thread#start0(),start0() 的本地方法逻辑中会调用 run() 方法
  3. 直接调用 Thread#run() 方法或者 Runnable#run() 方法不会创建新线程执行任务,而是在主线程直接串行执行,如果要创建新线程执行任务,需要调用 Thread#start() 方法

调用逻辑图如下:

在这里插入图片描述

Thread#run() 源码如下:

// 自定义重写 Thread#run() 或者传入 Runnable,最终都会调用该线程的 run() 方法逻辑
// 如果传入了 Runnable 就会走进这个方法运行 target.run(),有点装饰器模式的感觉
@Override
public void run() {if (target != null) {target.run();}
}

至于为何最终 start0() 还是调用了 Thread#run(),这就需要去看 jdk 源码了,我刚好也硬着头皮去挖了下:

  1. 首先看到 Thread.c 文件中 registerNatives 里面注册的这些本地方法,start0() 会去调用 JVM_StartThread在这里插入图片描述

  2. 在 jvm.cpp 文件中找出 JVM_StartThread 方法,其底层调用 new JavaThread()方法
    在这里插入图片描述

    最终该方法真的会去 thread.cpp 里调用创建操作系统线程的方法 os::create_thread

在这里插入图片描述

  1. new JavaThread() 方法里面会引用 jvm.cpp 文件中的 thread_entry 方法,这个方法最终就会调用 vmSymbols::run_method_name(),看起来是个虚拟机内注册的方法

    在这里插入图片描述

    全局检索一下,其实就是在 vmSymbols.hpp 头文件中定义的许多通用方法和变量,run 方法刚好是其中定义的一个,也就是 Thread#run()。
    还可以看到许多其他常见的方法,比方说类的初始化方法,是 jvm 第一次加载 class 文件时调用,包括静态变量初始化语句和静态块执行。参考init和clinit何时调用
    在这里插入图片描述

2.5. sleep() 方法

Thread#sleep() 方法会让当前线程休眠一段时间,单位为毫秒,由于是 static 方法,所以是让直接调用 Thread.sleep() 的休眠,这里需要注意的是:

调用 sleep() 方法使线程休眠以后,不会释放自己占有的锁。

// 本地方法,真正让线程休眠的方法
public static native void sleep(long millis) throws InterruptedException;/*** Causes the currently executing thread to sleep (temporarily cease* execution) for the specified number of milliseconds plus the specified* number of nanoseconds, subject to the precision and accuracy of system* timers and schedulers. The thread does not lose ownership of any* monitors.** @param  millis*         the length of time to sleep in milliseconds** @param  nanos*         {@code 0-999999} additional nanoseconds to sleep** @throws  IllegalArgumentException*          if the value of {@code millis} is negative, or the value of*          {@code nanos} is not in the range {@code 0-999999}** @throws  InterruptedException*          if any thread has interrupted the current thread. The*          <i>interrupted status</i> of the current thread is*          cleared when this exception is thrown.*/
public static void sleep(long millis, int nanos)
throws InterruptedException {if (millis < 0) {throw new IllegalArgumentException("timeout value is negative");}if (nanos < 0 || nanos > 999999) {throw new IllegalArgumentException("nanosecond timeout value out of range");}if (nanos >= 500000 || (nanos != 0 && millis == 0)) {millis++;}// 调用本地方法sleep(millis);
}

2.6. join() 方法

这个方法是目前我觉得 Thread 里面最难理解的方法了,涉及到 synchronized 锁、wait、notify 原理,以及线程调用主体之间的辨析,参考 【Java】Thread类中的join()方法原理,我的理解如下:

首先看下 Thread#join() 方法的源码:

非静态方法,是类中的普通方法,比方说 Main 线程调用 ThreadA.join(),就是 Main 线程会等待 ThreadA 执行完成

// 调用方法,比方说 Main 线程调用 ThreadA.join(),就是 Main 线程会等待 ThreadA 执行完成
public final void join() throws InterruptedException {join(0);
}public final synchronized void join(long millis)
throws InterruptedException {long base = System.currentTimeMillis();long now = 0;if (millis < 0) {throw new IllegalArgumentException("timeout value is negative");}if (millis == 0) {// 该分支是无限期等待 ThreadA 结束,其实内部最后是在 ThreadA 结束时被 notify while (isAlive()) {wait(0);}} else {// 该分支时等待有限的时间,如果 ThreadA 在 delay 时间以后还未结束,等待线程也返回了while (isAlive()) {long delay = millis - now;if (delay <= 0) {break;}wait(delay);now = System.currentTimeMillis() - base;}}
}

重点关注下其中会出现的两个 wait():

首先我们知道,Object#wait() 需要放在 synchronized 代码块中执行,即获取到锁以后再释放掉锁。

这个 synchronized 锁就是在 Thread#join() 方法上,成员方法上加了 synchronized 说明就是 synchronized(this), 假设 Main 线程调用 ThreadA.join(),那么这个 this 就是指调用 ThreadA.join() 的 ThreadA 对象本身,最终效果就是,调用方 Main 线程持有了 ThreadA 对象的 Monitor 锁,被记录在 ThreadA 对象头上。

在这里插入图片描述

有了 Object#wait() 就需要有对应的 Object#notify() 将其唤醒,这又得看到 jvm 源码里面去了

在 openjdk/hotspot/src/share/vm/runtime/thread.cpp 的 JavaThread::exit 方法中,这其实是线程退出时会执行的方法,有个 ensure_join() 方法
在这里插入图片描述

ensure_join() 方法的源码如下:

上面的 this 就是指 ThreadA,就是下面方法入参中的 thread。可以看出来,当线程 ThreadA 执行完成准备退出时,jvm 会自动唤醒等待在 threadA 对象上的线程,在我们的例子中就是主线程。

在这里插入图片描述

总结如下:

Thread.join() 方法底层原理是 synchronized 方法 + wait/notify。主线程调用 ThreadA.join() 方法,通过 synchronized 关键字获取到 ThreadA 的对象锁,内部再通过 Object#wait() 方法等待,这里的执行方和调用方都是主线程,最终当 ThreadA 线程退出的时候,jvm 会自动 notify 唤醒等待在 ThreadA 上的线程,也就是主线程。

2.7. interrupt() 方法

Thread#interrupt 是中断被调用线程的方法,它通过设置线程的中断标志位来中断被调用线程,通常调用会抛出 java.lang.InterruptedException 异常。

这种中断线程的方法比较安全,能够使正在执行的任务继续能够执行完,而不像 stop() 方法那样强制关闭。

public void interrupt() {if (this != Thread.currentThread())checkAccess();synchronized (blockerLock) {Interruptible b = blocker;if (b != null) {interrupt0();           // Just to set the interrupt flagb.interrupt(this);return;}}// 调用本地方法中断线程interrupt0();
}

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

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

相关文章

Spring自定义参数解析器

在这篇文章中&#xff0c;我们认识了参数解析器和消息转换器&#xff0c;今天我们来自定义一个参数解析器。 自定义参数解析器 实现HandlerMethodArgumentResolver的类&#xff0c;并注册到Spring容器。 Component&#xff0f;&#xff0f;注册到Spring public class UserAr…

Java集合必知必会:热门面试题汇编与核心源码(ArrayList、HashMap)剖析

写在前面 &#x1f525;我把后端Java面试题做了一个汇总&#xff0c;有兴趣大家可以看看&#xff01;这里&#x1f449; ⭐️在无数次的复习巩固中&#xff0c;我逐渐意识到一个问题&#xff1a;面对同样的面试题目&#xff0c;不同的资料来源往往给出了五花八门的解释&#…

【Linux进程控制】自主Shell

目录 自主shell实现 获取基本变量 实现命令行 获取用户命令字符串 命令行字符串分割 内建命令CD() chdir getcwd putenv 检查是否为内建命令 检查是否为重定向 执行命令 主函数设置 测试用例 项目代码 自主shell实现 根据之前学的内容&#xff0c;我们已经可以模…

【学习笔记】SSL/TLS安全机制之CAA

1、概念界定 CAA全称Certificate Authority Authorization&#xff0c;即证书颁发机构授权&#xff0c;每个CA都能给任何网站签发证书。 2、CAA要解决的问题 例如&#xff0c;蓝色网站有一张橙色CA颁发的证书&#xff0c;我们也知道还有许多其他的CA&#xff1b;中间人可以说服…

网址链接能做成二维码吗?在线网址二维码生成的操作技巧

现在用二维码能够展示很多的内容&#xff0c;将内容放入二维码后&#xff0c;通过扫码的方式获取内容会更加的方便快捷&#xff0c;简化获取内容的流程。比如在分享网上内容时&#xff0c;可以将链接生成二维码的方式来让用户扫码访问网页&#xff0c;那么网址转二维码具体该怎…

【BetterBench博士】2024年中国研究生数学建模竞赛 E题:高速公路应急车道紧急启用模型 问题分析

2024年中国研究生数学建模竞赛 E题&#xff1a;高速公路应急车道紧急启用模型 问题分析 更新进展 【BetterBench博士】2024 “华为杯”第二十一届中国研究生数学建模竞赛 选题分析 【BetterBench博士】2024年中国研究生数学建模竞赛 E题&#xff1a;高速公路应急车道紧急启用…

【垃圾识别系统】Python+卷积神经网络算法+人工智能+深度学习+计算机毕设项目选题+TensorFlow+图像识别

一、介绍 垃圾识别分类系统。本系统采用Python作为主要编程语言&#xff0c;通过收集了5种常见的垃圾数据集&#xff08;‘塑料’, ‘玻璃’, ‘纸张’, ‘纸板’, ‘金属’&#xff09;&#xff0c;然后基于TensorFlow搭建卷积神经网络算法模型&#xff0c;通过对图像数据集进…

Qt窗口——对话框

文章目录 对话框自定义对话框对话框分类消息对话框QMessageBox使用示例自定义按钮快速构造对话框 颜色对话框QColorDialog文件对话框QFileDialog字体对话框QFontDialog输入对话框QInputDialog 对话框 对话框可以理解成一个弹窗&#xff0c;用于短期任务或者简洁的用户交互 Qt…

2024华为杯研赛D题分析

2024华为杯研究生数学建模D题分析如下&#xff0c;完整版本在文末名片

【HTTP】请求“报头”,Referer 和 Cookie

Referer 描述了当前这个页面是从哪里来的&#xff08;从哪个页面跳转过来的&#xff09; 浏览器中&#xff0c;直接输入 URL/点击收藏夹打开的网页&#xff0c;此时是没有 referer。当你在 sogou 页面进行搜索时&#xff0c;新进入的网页就会有 referer 有一个非常典型的用…

扎克伯格的未来愿景:用智能眼镜引领数字社交新时代

Meta Connect 2024大会前夕&#xff0c;创始人马克扎克伯格的90分钟播客访谈&#xff0c;为我们描绘了Meta未来的蓝图。这场访谈&#xff0c;不仅是大会的热身&#xff0c;更是对科技未来的一次深刻洞察。 人工智能 - Ai工具集 - 未来办公人的智能办公生活导航网 扎克伯格的未…

nacos适配人大金仓的数据库

前言 在微服务架构中&#xff0c;服务发现和配置管理是关键组件。Nacos作为一个动态服务发现和配置管理平台&#xff0c;支持多种数据库作为其后端存储。本文将探讨如何在Nacos中适配人大金仓数据库&#xff0c;以及在此过程中的最佳实践。 Nacos简介 Nacos&#xff08;Nami…

二分查找算法(1) _二分查找_模板

个人主页&#xff1a;C忠实粉丝 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 C忠实粉丝 原创 二分查找算法(1) _二分查找模板 收录于专栏【经典算法练习】 本专栏旨在分享学习算法的一点学习笔记&#xff0c;欢迎大家在评论区交流讨论&#x1f48c; 1. 二…

Redis——持久化策略

Redis持久化 Redis的读写操作都是在内存上&#xff0c;所以Redis性能高。 但是当重启的时候&#xff0c;或者因为特殊情况导致Redis崩了&#xff0c;就可能导致数据的丢失。 所以Redis采取了持久化的机制&#xff0c;重启的时候利用之间持久化的文件实现数据的恢复。 Redis提…

[Matplotlib教程] 02 折线图、柱状图、散点图教程

基于MFCC和CNN的语音情感识别 2 折线图、柱状图、散点图2.1 折线图2.1.1 简单折线图2.1.1 线形和Markevery2.1.2 带误差棒的折线图2.1.3 区间填充和透明度 2.2 柱状图2.2.1 分组柱状图2.2.2 堆叠柱状图2.2.3 横向柱状图 2.3 散点图 我们的网站是 菜码编程&#xff0c;我们的q群…

django项目添加测试数据的三种方式

文章目录 自定义终端命令Faker添加模拟数据基于终端脚本来完成数据的添加编写python脚本编写shell脚本执行脚本需要权限使用shell命令来完成测试数据的添加 添加测试数据在工作中一共有三种方式&#xff1a; 可以根据django的manage.py指令进行[自定义终端命令]可以采用第三方…

2024华为杯数学建模研赛F题建模代码思路文章研究生数学建模

截止2024.8.21 12点 已更新F全部小问的建模和问题一的代码 #### https://docs.qq.com/doc/DVVBUREF2SmFhRUl3F题: 问题1&#xff1a;卫星轨道根数与运动学关系的数学模型 从卫星的轨道根数计算出它在特定时刻的三维位置和速度。轨道根数包括&#xff1a; 1.计算卫星的轨道半径…

Android Studio开发发布教程

本文讲解Android Studio如何发布APP。 在Android Studiobuild菜单栏下点击Generate Singed Bundle/APK…打开对话框。 选择APK点击Next 点击Create New...进行创建

【赵渝强老师】K8s的DaemonSets控制器

DaemonSet控制器相当于在节点上启动了一个守护进程。通过使用DaemonSet可以确保一个Pod的副本运行在 Node节点上。如果有新的Node节点加入集群&#xff0c;DaemonSet也会自动给新加入的节点增加一个Pod的副本&#xff1b;反之&#xff0c;当有Node节点从集群中移除时&#xff0…

KMP整理+个人推导+快速上手理解

整理了一下KMP的写法&#xff1a; 这个是我自己写的&#xff08;个人推导&#xff0c;可能在时间复杂度上表现较弱&#xff0c;但是非常帮助初学者进行理解&#xff01;&#xff09; 下面是代码&#xff0c; ne 是next数组。我这个next数组表示的是&#xff1a; ne[i] : 当s…