线程知识总结(一)

1、概述

1.1 进程与线程

进程是程序运行时,操作系统进行资源分配的最小单位,包括 CPU、内存空间、磁盘 IO 等。从另一个角度讲,进程是程序在设备(计算机、手机等)上的一次执行活动,或者说是正在运行中的程序。

一个程序进入内存运行时,它就变成一个进程。进程是处于运行中的程序,它拥有自己独立的资源和地址空间,在没有经过进程本身允许的情况下,进程不可以直接访问其他进程的地址空间。同时多个进程可以在单个处理器上并发执行,多个进程之间互不影响。

线程是进程的一个实体,是 CPU 调度的最小单位。自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器、一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。

使用多线程解决了多任务同时运行的问题,并且,共享变量使得线程间的通信要比进程间通信更有效、更容易。此外,在一些操作系统中,与进程相比,线程更“轻量级”,创建、撤销一个线程比启动新进程的开销要小得多。

当然,线程太多,来回切换也会导致执行效率降低。

其实应用程序的执行都是 CPU 在做着快速的切换完成的,这个切换是随机的。在某一个时刻,CPU 只在执行一个线程,但是由于它的执行速度非常快,在毫秒级别,因此人无法感知到它在一个时间片中其实只在执行一个任务,在时间片结束后又去切换执行另一个任务。

1.2 并行与并发

并发是指在同一时刻只能有一条指令执行,但多个进程指令被快速轮换执行,使得宏观上具有多个进程同时执行的效果;而并行是指在同一时刻,有多条指令在多个处理器上同时执行。

1.3 JVM 中的多线程解析

JVM 启动时就启动了多个线程,至少有两个线程可以分析的出来:

  1. 执行 main 方法的线程,即主线程。该线程的任务代码都定义在 main 方法中。主线程执行完任务,其所在进程也就关闭了。
  2. 负责垃圾回收的线程。系统会自己决定何时回收垃圾,你也可以通过 System.gc() 通知垃圾回收器来回收。但是这个也不是立即回收垃圾,也是在调用之后的一定时间内。另外 Object 中还有一个 finalize() 方法进行垃圾回收。

实际上,JVM 启动的完整线程有以下这些:

  1. main:main线程,用户程序入口
  2. Reference Handler:清除Reference的线程
  3. Finalizer:调用对象finalize方法的线程
  4. Signal Dispatcher:分发处理发送给JVM信号的线程
  5. Attach Listener:内存 dump,线程 dump,类信息统计,获取系统属性等
  6. Monitor Ctrl-Break:监控 Ctrl-Break 中断信号的

此外,多线程运行时的示意图如下:

请添加图片描述

说明:

  1. 当前有3个线程:main 线程、Thread-1、Thread-2,它们每个都维护了自己的方法栈(run 方法在栈底)。在哪个线程调用了方法,这个方法就会进入哪个线程。
  2. 如果 main 线程先于另外两个线程执行完,JVM 不会结束,而是等所有线程都运行完。
  3. 在3个线程都运行的前提下,如果在某个线程中发生了异常导致该线程停止,不会影响其它线程,其它线程该怎么执行就怎么执行

2、使用

2.1 创建线程

创建线程的目的是为了开启一条执行路径,去运行指定的代码(即该线程的任务)和其他代码实现同时运行。

创建线程的方法有两种:继承 Thread 类和实现 Runnable 接口,实现 Callable 接口严格讲是属于第二种方式,不能单独作为一种方法。

我们先来了解下 Callable 的基本用法再解释上述观点的原因。

Callable 的用法

Callable 是一个函数式接口,有泛型限制,该泛型参数类型与作为线程执行体的 call() 返回值类型相同。call() 比 Runnable 的 run() 功能要更强大,因为它可以有返回值,并且可以声明抛出异常

虽然 call() 也是线程执行体,但是由于 Callable 接口不是 Runnable 的子接口,所以 Callable 并不能直接作为 Thread 的 target,而是要借助 Future 接口。

Future 接口代表 call() 的返回值,而且 Future 的实现类 FutureTask 也实现了 Runnable 接口,可以作为 Thread 的 target。Future 中定义了如下的公共方法控制与它关联的 Callable 任务:

在这里插入图片描述

创建并启动有返回值的线程的步骤:

示例如下:

// Callable 后的参数类型为 String,意味着 call() 的返回值类型为 String
public class CallableTest implements Callable<String> {public static void main(String[] args) {new CallableTest().test();}/*** Callable 的 call() 类似于 Runnable 的 run(),只不过前者有返回值而前者没有。** 此外,Future 接口可以控制 Runnable/Callable 取消执行任务、查询任务是否完成、* 获取任务执行结果(通过阻塞方法 get 获取结果)。** 由于 Future 接口不能直接实例化,所以一般都是使用 FutureTask,它实现了 RunnableFuture* 接口,RunnableFuture 又继承了 Runnable 和 Future,所以它既可以作为 Runnable 被线程执行,* 又可以作为Future得到Callable的返回值。*/private void test() {// 将 Callable 包装进 FutureTask 后交给 ThreadFutureTask<String> futureTask = new FutureTask<>(this);new Thread(futureTask).start();try {System.out.println(futureTask.get());} catch (InterruptedException | ExecutionException e) {e.printStackTrace();}}// 任务是读取文件内容,并发它转换成字符串作为返回值@Overridepublic String call() throws Exception {StringBuffer stringBuffer = new StringBuffer();try (FileInputStream fileInputStream = new FileInputStream("filepath");FileChannel inChannel = fileInputStream.getChannel()) {ByteBuffer byteBuffer = ByteBuffer.allocate(256);while (inChannel.read(byteBuffer) != -1) {byteBuffer.flip();Charset charset = Charset.forName("GBK");CharBuffer charBuffer = charset.decode(byteBuffer);stringBuffer.append(charBuffer);byteBuffer.clear();}}return stringBuffer.toString();}
}

原因解释

我们结合 Thread 的源码解释下原因。首先,Thread 的构造方法中并没有 Callable 作为参数的:

只有空参和接收 Runnable 的构造方法:

public class Thread implements Runnable {/* What will be run. */private Runnable target;public Thread() {// nextThreadNum() 初始为 0,可以看到线程编号在创建线程时就已经确定init(null, null, "Thread-" + nextThreadNum(), 0);}public Thread(Runnable target) {// init() 会将参数 target 赋值给成员变量的 targetinit(null, target, "Thread-" + nextThreadNum(), 0);}public void run() {if (target != null) {target.run();}}
}

关注运行任务的 run():

  • 如果使用继承 Thread 的方式创建线程,那么 run() 被重写,执行任务时就按照 Thread 子类的 run() 去执行。
  • 如果使用实现 Runnable 的方式创建线程,run() 在判断 target 不为空之后会运行 target 的 run()。可以认为 Runnable 就是对线程的任务进行了对象的封装。

因为 FutureTask -> RunnableFuture -> Runnable & Future,而 Callable 需要交给 FutureTask 才能执行,所以实现 Callable 接口这种创建线程的方式在实现 Runnable 接口的范畴内,不能作为一种单独的创建方式。

两种创建线程的方式对比

两种方式各有优缺点,通常还是使用实现 Runnable 接口的方式:

  • 继承 Thread 类方式编写简单如果要访问当前线程无须使用 Thread.currentThread(),直接使用 this 即可。劣势是不能再继承其它父类。
  • 实现 Runnable 接口方式则可以继承其它类,并且多个线程可以共享同一个 target 对象,非常适合多个相同线程来处理同一份资源,从而将 CPU、代码和数据分开,形成清晰的模型。缺点就是编程稍复杂,必须使用 Thread.currentThread() 访问当前线程。

2.2 停止线程

线程会因为如下两个原因之一被停止:

  1. run() 正常退出而自然死亡
  2. 因为一个没有捕获的的遗产终止了 run() 而意外死亡

推荐使用 Thread 的 interrupt() 配合 isInterrupted()/interrupted() 来停止线程。

interrupt()

interrupt() 会请求线程停止,注意是请求,而不是立即停止线程。该方法会将线程中的中断状态标记位置位,等待线程通过 isInterrupted()/interrupted() 检查该标记位来进行响应。isInterrupted() 和 interrupted() 都会在标记位被置位的情况下返回 true,不同点在于前者是对象方法,而后者是一个静态方法,并且在调用后会将标记位改写为 false。

在使用上述方法时需要注意,如果在中断标记位为 true 的情况下执行阻塞方法(如 Thread.sleep()、Thread.join()、Object.wait()),这些阻塞方法会抛出 InterruptedException,并且在抛出异常后立即将线程的中断标示位清除重置为 false:

    public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(new Runnable() {@Overridepublic void run() {while (!Thread.currentThread().isInterrupted()) {System.out.println("Thread is running...");try {Thread.sleep(500);} catch (InterruptedException e) {System.out.println(Thread.currentThread().isInterrupted());e.printStackTrace();// Thread.currentThread().interrupt();}}}});thread.start();Thread.sleep(1000);thread.interrupt();System.out.println("interrupt in main!");}

如果不打开 catch 中被注释掉的 Thread.currentThread().interrupt(),线程是无法被中断的,因为 sleep() 如果发现中断标记位为 true 会抛出异常并将其清除为 false:

Thread is running...
Thread is running...
Thread is running...
Thread is running...
interrupt in main!
false
Thread is running...
java.lang.InterruptedException: sleep interruptedat java.lang.Thread.sleep(Native Method)at com.demo.thread.multi.InterruptDemo$1.run(InterruptDemo.java:13)at java.lang.Thread.run(Thread.java:748)
Thread is running...
Thread is running...
// 线程继续运行输出 Thread is running...

从这里也能看到子线程其实是在主线程结束后才消亡的,一定注意要让线程能执行完,否则这个线程会阻止已经运行完的主线程所在的进程结束

在抛出 InterruptedException 的 catch 代码块中调用 interrupt() 中断线程是基本操作。

自定义标记位

interrupt() 基本上是我们推荐的,唯一的中断线程的方法。或许有人会问,自己在线程中定义一个中断标记位不是也能实现线程中断嘛,像这样:

class StopThread implements Runnable {private boolean flag = true;public void run() {while (flag) {System.out.println(Thread.currentThread().getName() + "......++++");}}// 外部调用注入方法控制标记位进而停止线程public void setFlag(boolean flag) {this.flag = flag;}
}

一般情况下确实可以,但如果线程中执行的代码有类似 wait() 这样的阻塞方法,那么该线程就会进入等待状态,在线程池中等待唤醒,在没有其它线程唤醒它的情况下,它就无法通过标记位的方式结束线程:

	public synchronized void run() {while (flag) {try {wait();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "......++++");}}

在这种情况下,使用 interrupt() 会更好,因为:

  1. 一般的阻塞方法,如 sleep()、wait() 等本身就支持中断的检查
  2. 检查中断标记位和自定义的标志位没什么区别,用中断标记位还可以避免声明自定义的标志位,减少资源的消耗
  3. interrupt() 方法会将线程从阻塞状态强制恢复到运行状态中来,让线程具备 cpu 的执行资格,但是强制动作会发生 InterruptedException,需要处理

中断异常是如何被抛出的

我们以 Thread.sleep() 为例,进入源码看下中断异常是如何被抛出的:

    /*** sleep 期间,线程不会失去已经获取到的同步锁。** @throws  InterruptedException*          如果任何线程中断了当前线程,那么会抛出这个异常并且*          清除掉当前线程的中断状态。*/public static native void sleep(long millis) throws InterruptedException;

想要查看 sleep() 的 native 源码,要先在 src/share/native/java/lang/Thread.c 文件中,找到 sleep() 在 JVM 中对应的方法 JVM_Sleep:

#include "jni.h"
#include "jvm.h"#include "java_lang_Thread.h"#define THD "Ljava/lang/Thread;"
#define OBJ "Ljava/lang/Object;"
#define STE "Ljava/lang/StackTraceElement;"
#define STR "Ljava/lang/String;"#define ARRAY_LENGTH(a) (sizeof(a)/sizeof(a[0]))static JNINativeMethod methods[] = {{"start0",           "()V",        (void *)&JVM_StartThread},{"stop0",            "(" OBJ ")V", (void *)&JVM_StopThread},{"isAlive",          "()Z",        (void *)&JVM_IsThreadAlive},{"suspend0",         "()V",        (void *)&JVM_SuspendThread},{"resume0",          "()V",        (void *)&JVM_ResumeThread},{"setPriority0",     "(I)V",       (void *)&JVM_SetThreadPriority},{"yield",            "()V",        (void *)&JVM_Yield},{"sleep",            "(J)V",       (void *)&JVM_Sleep},{"currentThread",    "()" THD,     (void *)&JVM_CurrentThread},{"countStackFrames", "()I",        (void *)&JVM_CountStackFrames},{"interrupt0",       "()V",        (void *)&JVM_Interrupt},{"isInterrupted",    "(Z)Z",       (void *)&JVM_IsInterrupted},{"holdsLock",        "(" OBJ ")Z", (void *)&JVM_HoldsLock},{"getThreads",        "()[" THD,   (void *)&JVM_GetAllThreads},{"dumpThreads",      "([" THD ")[[" STE, (void *)&JVM_DumpThreads},{"setNativeName",    "(" STR ")V", (void *)&JVM_SetNativeThreadName},
};#undef THD
#undef OBJ
#undef STE
#undef STR// 注册 methods[] 中的 native 方法
JNIEXPORT void JNICALL
Java_java_lang_Thread_registerNatives(JNIEnv *env, jclass cls)
{(*env)->RegisterNatives(env, cls, methods, ARRAY_LENGTH(methods));
}

然后去 jvm.cpp 文件中找到这个方法:

可以看到如果线程的中断标记位已经为 true,调用 sleep 方法就会抛出 InterruptedException。在抛出 InterruptedException 之前,中断标记位会被清除为 false。

3、线程的状态

3.1 状态定义与状态转移

线程状态也被称为生命周期,指的是 JVM 中的线程状态,而不是操作系统的。Thread 中定义的枚举类 State 规定了线程的 6 种状态:

  1. 初始(NEW):新创建了一个线程对象,但还没有调用 start()。这时仅仅由虚拟机分配内存并初始化变量值。如果此时错误地调用了 run(),该线程就不再处于初始状态,不能再调用 start()。
  2. 可运行(RUNNABLE):Java 线程中将就绪(ready)和运行中(running)两种状态统称为“可运行”。
    线程对象创建后,其他线程(如主线程)调用了该对象的 start() 后会进入就绪状态,虚拟机会创建方法调用栈和程序计数器。该状态的线程位于可运行线程池中,等待被线程调度选中,获取 CPU 的使用权。即线程可以运行,但是尚未运行。
    就绪状态的线程在获得 CPU 时间片后,开始执行 run() 就变为运行中状态(running)。
  3. 阻塞(BLOCKED):表示线程阻塞于锁。
  4. 等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(如通知或中断)。
  5. 等待超时(TIMED_WAITING):该状态不同于 WAITING,它可以在指定的时间后自行返回,也就是计时等待。
  6. 终止(TERMINATED):表示该线程已经执行完毕,包括 run() 或 call() 执行完成,线程正常结束;线程抛出未捕获的 Exception 或 Error;直接调用了 stop() 结束线程(容易死锁,不推荐)。

状态转移图如下所示:

说明:

  1. 主线是初始->运行->终止,new 创建的新线程要调用 start() 才能进入运行状态。运行状态内部又分为运行中(具备执行权)和就绪(具备执行资格但没有执行权)两种状态。注意 Java 中是把运行中和就绪统一视为运行状态,但是在操作系统的观点中,认为这两个状态是分开的、两个独立的状态
  2. 运行状态的线程可以通过调用 Object.wait() 或 Thread.sleep() 等方法进入等待状态,方法上加时间参数的会进入等待超时状态。等待状态下的线程没有执行资格,需要通过 notify()、notifyAll() 等方法唤醒。
  3. 阻塞状态(具备执行资格但无执行权)只有一种情况,就是等待获取 synchronized 锁,拿到锁之后就变成了运行状态(阻塞式 IO 方法应该是这种情况?)。注意使用显式锁 Lock 等待锁时,进入的是等待/等待超时状态,因为其底层使用的是 LockSupport 类实现的。因此系统中能让线程进入阻塞状态的有且仅有 synchronized 关键字。
  4. 阻塞状态是一种被迫的等待(因为拿不到锁只能等着),而等待状态是一种主动的等待(主动调用 wait() 或 sleep())。

此外还有几点注意事项:

  1. 主线程结束并不会影响其它线程。
  2. isAlive() 可以测试某个线程是否存活,就绪、运行、阻塞状态返回 true,新建、死亡返回 false。
  3. 不要对已经死亡的线程调用 start(),也不要对新建的线程调用两次 start(),否则会引发 ILLegalThreadStateException。

针对以上情况,当发生如下特定情况时可以解除上面的阻塞,使线程重新进入就绪状态:

3.2 涉及的方法介绍

join()

可以通过 join() 控制线程的执行顺序,哪个线程执行到了 join() 就释放执行权并冻结在线程池中,等调用了 join() 的线程执行完后,才恢复可执行状态,与其它线程争夺执行权。比如说:

public static void main(String[] args) throws Exception{Demo d = new Demo();Thread t1 = new Thread(d);Thread t2 = new Thread(d);t1.start();t2.start();t1.join();//t1线程要申请加入进来,运行。临时加入一个线程运算时可以使用join方法。for(int x=0; x<50; x++){System.out.println(Thread.currentThread()+"....."+x);}}
}

主线程开启了 t1、t2 两个线程,在执行 t1.join() 之前,是三个线程在轮番运行的。在主线程中执行了 t1.join() 后,主线程释放执行权,冻结在线程池。等待 t1 执行完毕后,恢复可执行状态,与 t2 争夺执行资格,轮番运行。

如果在 A 线程中调用了 B 线程的 join() 方法,那么 A 线程将被阻塞直到 B 线程执行完。它有三种重载形式:

第三种形式很少被用到,因为程序、操作系统和计算机硬件都无法精确到纳秒。

sleep()

sleep() 用于线程睡眠,调用该方法可以让线程暂停一段时间进入等待状态,它有两种重载形式:

同样是因为程序、操作系统和硬件设备无法精确到纳秒,因此第二个方法很少被使用。

处于睡眠时间内的线程不会获得执行的机会,即使系统中没有其它可执行的线程,处于 sleep() 中的线程也不会执行;已经获得锁的线程如果执行了 sleep(),只会释放执行权,但不会释放锁。

yield()

yield() 用于线程让步,它也可以让当前正在运行的线程暂停,但它不会使线程进入等待状态,只是将该线程转入可运行状态,然后让系统的线程调度器重新调度一次。完全可能的情况是:某个线程调用了 yield() 暂停之后,调度器又将其调度出来重新执行。

实际上,当 A 线程调用了 yield() 之后,只有优先级大于等于 A 的处于可运行状态的线程才会获得执行机会。

暂停当前正在执行的线程对象,释放执行权,然后让包括自己在内的所有线程再次争夺执行权。这样做会更和谐,不会一直在执行同一个线程。

sleep() 与 yield() 的区别:

其它方法

Object.wait() 与 Thread.sleep() 的对比:

  1. wait() 可以指定时间也可以不指定,sleep() 必须指定时间。
  2. 在同步中时,对 cpu 的执行权和锁的处理不同。wait() 释放执行权,释放锁;sleep() 释放执行权,不释放锁。
class Demo {void show() {synchronized(this) {wait();//t0 t1 t2}}void method() {synchronized(this)//t4{notifyAll();}//t4}
}

同步中谁有锁谁执行,如果 t0 t1 t2 都卡在 wait() 并被 t4 唤醒,虽然 3 个都活了,但是只有一个能持锁执行代码。

此外,我们经常会调用 Thread 的 toString() 输出线程信息,线程的字符串表现形式,包括线程名称、优先级和线程组:

    public String toString() {ThreadGroup group = getThreadGroup();if (group != null) {return "Thread[" + getName() + "," + getPriority() + "," +group.getName() + "]";} else {return "Thread[" + getName() + "," + getPriority() + "," +"" + "]";}}

4、线程属性

4.1 优先级

每个 Java 线程都有一个优先级,可以通过 Thread.setPriority() 将线程优先级设置在 MIN_PRIORITY(数值为1)和 MAX_PRIORITY(数值为10)之间,默认优先级为 MIN_PRIORITY(数值为5)。

线程调度器会优先选择优先级高的线程来运行。但是线程优先级是高度依赖于系统的。当 JVM 依赖于宿主机平台的线程实现机制时,Java 线程的优先级会先被映射到宿主机平台的优先级上。例如 Windows 有 7 个优先级,而在 Oracle 为 Linux 提供的 JVM 中,线程的优先级被忽略——所有线程具有相同的优先级。

如果确实要使用优先级,需要注意,如果高优先级的线程没有进入非活动状态(阻塞或等待),低优先级的线程可能永远也得不到执行,发生线程饥饿的情况(线程饥饿就是指低优先级的线程,总是拿不到执行时间)。

4.2 守护线程

守护线程也称为后台线程、精灵线程,它是在后台运行的线程,任务是为其它线程提供服务,JVM 的垃圾回收线程就是典型的守护线程。此外,守护线程也可用于发送计时信号或清空过时的高速缓存。

可以使用 Thread 的 setDeamon(true) 将一个线程设置为守护线程(但是必须在该线程启动之前,否则会引发 ILLegalThreadStateException),isDeamon() 用来判断是否是守护线程。

主线程默认是前台线程,但不是所有线程默认都是前台线程,规则是:前台线程创建的子线程默认是前台线程,守护线程创建的线程默认是后台线程

守护线程也会去争抢同步锁。

守护线程的特征为当所有前台线程死亡后,虚拟机会退出并通知后台线程死亡。假如在主线程中开启了一个执行耗时操作的守护线程,那么很有可能守护线程的任务并不会执行完,因为主线程不会等待守护线程,只要主线程跑完了,守护线程也会自动消亡。但如果在主线程中启动一个非守护线程,那么主线程会等待该子线程执行完任务。

永远不要让守护线程去访问文件、数据库这样的固有资源,因为他会在任何时候甚至在一个操作的中间被中断。

4.3 线程组与未处理的异常

Java 允许程序直接对 ThreadGroup 进行控制。如果没有显式指定一个线程属于哪个线程组,那么它就属于默认线程组,即与创建它的线程在同一线程组。线程中途不能改变它所属的线程组

以下构造方法用来指定新创建的线程属于哪个线程组:

在这里插入图片描述

以上方法也可以指定线程组的名字,这个名字也不能中途更改。

常用的操作线程组的方法:

ThreadGroup 实现了一个接口 Thread.UncaughtExceptionHandler,该接口用于处理未捕获的异常。

线程的 run() 不能抛出任何受查异常(刨去非受查异常剩余的异常),而非受查异常(所有派生于 Error 或 RuntimeException 的异常)会导致线程终止。

线程因为异常终止之前,会将异常传递到未捕获异常的处理器中,该处理器必须实现 Thread.UncaughtExceptionHandler 接口,并且通过 Thread 的 setUncaughtExceptionHandler() 为某一个线程设置处理器,或者用静态的 setDefaultUncaughtExceptionHandler() 为所有线程设置一个默认的处理器。如果没有调用以上方法给线程设置处理器,那么线程的处理器就是该线程的 ThreadGroup 对象。

UncaughtExceptionHandler 接口的唯一方法 uncaughtException() 会按照如下优先顺序操作:

  1. 如果该线程组有父线程组,则调用父线程组的 uncaughtException()
  2. 如果 Thread 的 getDefaultUncaughtExceptionHandler() 返回一个非空处理器,则调用该处理器
  3. 如果 Throwable 是 ThreadDeath 的一个实例,就什么都不做,否则,就将线程名字以及 Throwable 的栈轨迹输出到 System.err 上。

其中,最后一点的栈轨迹就是应用发生崩溃时我们看到的调用栈信息。

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

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

相关文章

【Python】使用Selenium 操作浏览器 自动化测试 记录

【自动化】Python SeleniumUtil 工具 开启开发者模式 自动安装油猴用户脚本等-CSDN博客文章浏览阅读389次。【自动化】Python SeleniumUtil 工具。https://blog.csdn.net/G971005287W/article/details/144565691?spm1001.2014.3001.5501【学习记录】浏览器指纹相关学习记录&am…

【Rust自学】4.4. 引用与借用

4.4.0 写在正文之前 这一节的内容其实就相当于C的智能指针移动语义在编译器层面做了一些约束。Rust中引用的写法通过编译器的约束写成了C中最理想、最规范的指针写法。所以学过C的人对这一章肯定会非常熟悉。 喜欢的话别忘了点赞、收藏加关注哦&#xff08;加关注即可阅读全文…

深入解析 StarRocks 物化视图:全方位的查询改写机制

小编导读&#xff1a; 本文将重点介绍如何利用物化视图进行查询改写。文章将全面介绍物化视图的基本原理、关键特性、应用案例、使用场景、代码细节以及主流大数据产品的物化视图改写能力对比。 物化视图在 StarRocks 中扮演着至关重要的角色&#xff0c;它是进行数据建模和加速…

2. petalinux-build失败

NOTE 解决因为网络原因产生的编译错误分享详细的解决步骤 报错的情况 因为网络原因产生编译错误 现象 找不到适合的包文件(No suitable stageing package found) 不能发现文件(Fetcher failure for URL) 解决方法 采用本地加载本地文件的方式&#xff0c;步骤如下 进入…

web实验二

web实验二 2024.12.19 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>青岛理工大学</title>&l…

WebSocket入门与结合redis

WebSocket是什么 WebSocket 是一种用于在客户端和服务器之间建立双向通信的协议&#xff0c;它能实现实时、持久的连接。与传统的 HTTP 请求响应模式不同&#xff0c;WebSocket 在建立连接后允许客户端和服务器之间相互发送消息&#xff0c;直到连接关闭。由于 WebSocket 具有…

Hive是什么,Hive介绍

官方网站&#xff1a;Apache Hive Hive是一个基于Hadoop的数据仓库工具&#xff0c;主要用于处理和查询存储在HDSF上的大规模数据‌。Hive通过将结构化的数据文件映射为数据库表&#xff0c;并提供类SQL的查询功能&#xff0c;使得用户可以使用SQL语句来执行复杂的​MapReduce任…

OpenHarmony和OpenVela的技术创新以及两者对比

两款有名的国内开源操作系统&#xff0c;OpenHarmony&#xff0c;OpenVela都非常的优秀。本文对二者的创新进行一个简要的介绍和对比。 一、OpenHarmony OpenHarmony具有诸多有特点的技术突破和重要贡献&#xff0c;以下是一些主要方面&#xff1a; 架构设计创新 分层架构…

Electron-Vue 开发下 dev/prod/webpack server各种路径设置汇总

背景 在实际开发中&#xff0c;我发现团队对于这几个路径的设置上是纯靠猜的&#xff0c;通过一点点地尝试来找到可行的路径&#xff0c;这是不应该的&#xff0c;我们应该很清晰地了解这几个概念&#xff0c;以下通过截图和代码进行细节讲解。 npm run dev 下的路径如何处理&…

基础入门-Web应用蜜罐系统堡垒机运维API内外接口第三方拓展架构部署影响

知识点&#xff1a; 1、基础入门-Web应用-蜜罐系统 2、基础入门-Web应用-堡垒机运维 3、基础入门-Web应用-内外API接口 4、基础入门-Web应用-第三方拓展架构 一、演示案例-Web-拓展应用-蜜罐-钓鱼诱使 蜜罐&#xff1a;https://hfish.net/ 测试系统&#xff1a;Ubuntu 20.04 …

springboot中Controller内文件上传到本地以及阿里云

上传文件的基本操作 <form action"/upload" method"post" enctype"multipart/form-data"> <h1>登录</h1> 姓名&#xff1a;<input type"text" name"username" required><br> 年龄&#xf…

智慧城市工程:相关学点、优势、未来发展

目录 相关学点&#xff1a; 智慧城市的优势 挑战与未来发展 智慧城市工程是利用现代信息技术和数据分析手段&#xff0c;提升城市管理和服务水平&#xff0c;实现城市运行的智能化、便捷化和高效化的一种新型城市发展模式。智慧城市通过整合物联网&#xff08;IoT&#xff0…

看板工具助力餐饮与酒店行业实现数字化转型,提升管理与运营效率

在餐饮与酒店行业&#xff0c;服务质量和客户体验是衡量企业成功的关键因素。随着客户需求的不断多样化以及市场竞争的加剧&#xff0c;传统的管理模式逐渐难以满足高效运营的需求。尤其在高峰期&#xff0c;如何优化内部流程、提高服务效率和响应速度&#xff0c;成为了许多餐…

2024年CCF 非专业级软件能力认证CSP-J/S 第二轮( 提高组) 染色(color)

完整题目内容可前往下方链接&#xff1a; 染色&#xff08;color&#xff09;_C_嗨信奥-玩嗨信息奥林匹克竞赛-少儿编程题库学习中心https://www.hixinao.com/tiku/cpp/show-4118.html 若需更多真题&#xff0c;可前往题库中心查找&#xff0c;题库中心涵盖白名单赛事真题&am…

OpenIPC开源FPV之Adaptive-Link天空端代码解析

OpenIPC开源FPV之Adaptive-Link天空端代码解析 1. 源由2. 框架代码2.1 消息机制2.2 超时机制 3. 报文处理3.1 special报文3.2 普通报文 4. 工作流程4.1 Profile 竞选4.2 Profile 研判4.2.1 回退策略4.2.2 保持策略 4.3 Profile 应用 5. 总结6. 参考资料7. 补充资料7.1 RSSI 和 …

labelme标签批量转换数据集json_to_dataset

文章目录 labelme标签批量转换数据集json_to_dataset转换原理单张图片转换多张图片批量转换bat脚本循环法 标注图片提取标注图片转单通道 labelme标签批量转换数据集json_to_dataset 转自labelme批量制作数据集教程。 转换原理 在安装了labelme的虚拟环境中有一个labelme_js…

Apache Kylin最简单的解析、了解

官网&#xff1a;Overview | Apache Kylin 一、Apache Kylin是什么&#xff1f; 由中国团队研发具有浓厚的中国韵味&#xff0c;使用神兽麒麟&#xff08;kylin&#xff09;为名 的一个OLAP多维数据分析引擎:&#xff08;据官方给出的数据&#xff09; 亚秒级响应&#xff…

01云计算HCIA学习笔记

笔者今年7月底考取了华为云计算方向的HCIE认证&#xff0c;回顾从IA到IE的学习和项目实战&#xff0c;想整合和分享自己的学习历程&#xff0c;欢迎志同道合的朋友们一起讨论&#xff01; 第一章 云计算概述 ICT&#xff1a;ICT是世界电信协会在2001年的全球会议中提出的一个综…

php生成图片

前提 开启dg2库 去掉前面的;注释&#xff0c;有的可能会带.dll后缀影响不大 extensiongd2代码 <?php $file imagecreate(100,50); //先生成图片资源$color imagecolorallocate($file,255,255,255); //白色$c imagecolorallocate($file,0,100,255);imagefill($file,0…

免费GIS工具箱:轻松将glb文件转换成3DTiles文件

在GIS地理信息系统领域&#xff0c;GLB文件作为GLTF文件的二进制版本&#xff0c;主要用于3D模型数据的存储和展示。然而&#xff0c;GLB文件的使用频率相对较低&#xff0c;这是因为GIS系统主要处理的是地理空间数据&#xff0c;如地图、地形、地貌、植被、水系等&#xff0c;…