Java JUC(一) 线程概念与常用方法

Java JUC(一) 线程概念与常用方法

一. JUC 基本概念

Java JUC(Java Util Concurrent) 是Java平台提供的一个并发编程工具包(java.util.concurrent),全称为Java Concurrency Utilities。这个工具包包含了一系列的类和接口,旨在帮助开发者更方便、更安全地进行多线程编程和并发操作。JUC在Java 5.0(也称为Java SE 5或JDK 1.5)中引入,并随着Java版本的更新而不断发展和完善。JUC的主要特点和功能包括:线程同步和锁、并发容器、线程池、原子类、同步工具类等。

1. 多线程基础

(1)线程与进程

在操作系统中,进程是资源分配的基本单位,而线程是系统调度的基本单位。进程是程序的一次执行过程,在Java中当我们启动程序时其实就是启动了一个 JVM 的进程,一般 main 函数所在的线程就是这个进程中的主线程。线程是进程中一次任务的执行过程,是更小的独立执行单位,一个进程中可以包含多个线程。在Java内存区域中,多个线程共享进程的堆和方法区 (JDK1.8 之后的元空间)资源,但是每个线程拥有独立的程序计数器、虚拟机栈和本地方法栈。

在这里插入图片描述

(2)并行与并发、同步与异步

  • 并发:两个及两个以上的任务在同一 时间段 内执行,比如单核CPU的任务调度与执行。
  • 并行:两个及两个以上的作业在同一 时刻 执行,比如多核CPU的任务调度与执行。

并行与并发最关键的点是:是否是 同时 执行。

  • 同步:方法调用在发出之后,在没有得到结果之前,该调用指令需要一直等待结果返回。
  • 异步:方法调用在发出之后,不用等待返回结果即可继续执行,该调用直接返回。

同步与异步是一种调用的结果通知方式,其关键区别在于调用发出后是否需要等待结果返回。

2. 线程底层原理

(1)用户态与内核态

内核态和用户态、内核空间和用户空间是操作系统为了保障系统安全性和稳定性而设立的重要概念。它们通过权限的划分和隔离,确保了操作系统内核的安全运行,同时也为用户程序提供了一个稳定、安全的运行环境。

  • 内核态与内核空间: 内核态是操作系统内核(特殊的系统资源控制软件程序) 所运行的模式。在内核态下,运行的代码可以无限制地访问所有处理器指令集以及全部内存和I/O空间。如果程序处于内核态,就意味着它可以执行任何CPU指令,访问任何内存地址,基本上拥有了所有的系统权限。同时,只允许操作系统内核可访问的特殊内存区域就是内核空间,内核空间中包含了操作系统内核的代码、模块和数据等,负责管理系统资源、处理系统调用和中断等关键任务。
  • 用户态和用户空间: 用户态是普通用户应用程序所运行的模式。在用户态下,运行的代码会受到诸多限制,程序只能访问映射其地址空间的页表项中规定的在用户态下可访问页面的虚拟地址,而不能直接访问系统数据和指令。除此之外,用户空间是普通应用程序可访问的除内核空间之外的其他内存区域。用户程序在用户空间中运行,进行自己的运算、调用操作系统提供的服务、使用设备等操作,而不会直接影响到操作系统或其他用户程序的运行。用户空间中的程序可以通过系统调用(内核提供的一组通用访问接口) 的方式与内核空间进行交互,以获取必要的系统资源和服务(CPU、内存、IO等)。

在这里插入图片描述

(2)用户线程与内核线程

  • 用户线程: 用户线程也称为用户级线程(User-Level Thread,ULT),是在用户空间中创建,并由用户应用程序通过线程库进行调度和管理的线程。用户线程的整个运行过程都由用户自己的程序代码进行实现,而不需要内核的直接参与,系统内核也无法感知到用户线程的存在。用户线程的优点是无需与内核交互,不用切换用户态与内核态,线程的操作效率高;而其缺点是实现复杂,且无论单一进程包含多少用户线程,操作系统实际调度的单位仍是该线程所属的进程,即只能并发执行一个线程,无法利用多核的优势。
  • 内核线程: 内核线程也称为内核级线程(Kernel-Level Thread,KLT),是在内核空间中,由操作系统内核直接支持的线程,它们是内核对象的一部分。内核线程的管理和调度都由操作系统内核来完成,并可以在多核上执行和调度。在内核线程实现下,用户应用程序可以通过系统调用接口来访问和操作内核线程,同时每个用户线程会在其生命期内被映射或绑定到一个内核线程上。内核线程的优点是多核调度、系统级优化、线程阻塞不影响其他线程执行;而其缺点是内核线程数量有限,且应用程序线程在用户态运行,此时线程的创建、管理和切换等都需要内核参与或进行系统调用,即从用户态转换为内核态,系统开销较大。

​ JDK 1.2 之前,Java 线程是基于绿色线程(Green Threads)库实现的,这是一种用户级线程(用户线程),也就是说 JVM 自己模拟了多线程的运行,而不依赖于操作系统。由于绿色线程和原生线程比起来在使用时有一些限制(比如绿色线程不能直接使用操作系统提供的功能如异步 I/O、只能在一个内核线程上运行无法利用多核等),在 JDK 1.2 及以后,Java 线程改为基于原生线程(Native Threads) 实现,也就是说 JVM 直接使用操作系统原生的内核级线程(内核线程)来实现 Java 线程,并由操作系统内核接管线程的调度和管理。

(3)Java线程启动分析

Java中线程启动的核心方法依赖于Thread.start() 方法,该方法最终会在底层创建并关联内核线程,并交由操作系统调度。其启动过程分析如下:

  1. 调用Thread.start()方法,该方法会先检查线程状态(避免重复启动),然后调用Java中定义的本地Native方法start0(),接着start0()方法中会找到与之绑定的JVM_StartThread()这个JVM函数进行执行(cpp);
  2. JVM_StartThread()函数最终会调用os::create_thread(...)函数(依旧是JVM函数),在Linux中创建线程时最终会调用到pthread_create(...)这个内核函数创建内核线程;
  3. 创建内核线程后,接着会去执行Thread::start(...)函数,使得Java线程和内核线程产生映射关系,并回调执行线程的run()方法;

二. 线程创建与使用

1. 线程创建

线程是执行线程体的容器,线程体是一个可运行的任务。严格来说,Java只有一种方式可以创建线程,那就是通过new Thread().start()创建和启动;而所谓的Runnable、Callable、FutureTask……等对象仅仅只是线程体,也就是提供给线程执行的不同类型的封装任务,并不属于真正的Java线程,它们的执行最终还是需要依赖于new Thread();本节主要介绍继承Thread类实现Runnable接口两种创建方式。

(1)继承Thread类

public class MyThread extends Thread{@Overridepublic void run() {System.out.println("Extend Thread...");}public static void main(String[] args) {//创建并启动new MyThread().start();}
}

(2)实现Runnable接口

public class MyThread implements Runnable{@Overridepublic void run() {System.out.println("Implements Runnable...");}public static void main(String[] args) {MyThread runnable = new MyThread();new Thread(runnable).start();}
}
//匿名内部类
public static void main(String[] args) {new Thread(new Runnable() {@Overridepublic void run() {System.out.println("Implements Runnable...");}}).start();
}

2. 线程使用

2.1 sleep方法

TypeMethodDescription
static voidsleep(long millis) throws InterruptedException使当前执行线程休眠指定毫秒(暂停执行),如果休眠过程中被其他线程中断则抛出InterruptedException异常
static voidsleep(long millis, int nanos) throws InterruptedException使当前执行线程休眠指定毫秒加上指定纳秒(暂停执行),如果休眠过程中被其他线程中断则抛出InterruptedException异常

sleep()方法是Thread类的一个静态Native方法,用于使当前线程休眠一段时间并让出CPU资源,该方法会使线程状态进入TIMED_WAITING状态Thread.State,下同),并在休眠时间到期后重新变回RUNNABLE状态以等待CPU调度运行。该方法的特点如下:

  • 不释放锁: 调用sleep方法时,当前线程会暂停执行,但不会释放它(线程)持有的任何锁
  • 不保证绝对精确性: sleep方法的精确性取决于系统的定时器和调度器的精度和准确性,因此实际暂停的时间可能稍有不同;
  • 不保证立即执行: 睡眠时间结束后的线程未必会立即执行,通常需要重新等待获取CPU调度的时间片;
public class methods_test {public static void main(String[] args) {Thread thread = new Thread("thread-1"){@Overridepublic void run() {try {// current thread sleepThread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}}};// 查看线程休眠状态thread.start();System.out.println("thread state : " + thread.getState()); // RUNNABLE// main thread sleeptry {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("thread state : " + thread.getState()); // TIMED_WAITING}
}

2.2 join方法

TypeMethodDescription
voidjoin() throws InterruptedException使当前线程等待方法调用线程执行结束
voidjoin(long millis) throws InterruptedException使当前线程等待方法调用线程执行结束,最多等待n毫秒,当millis=0时等价于join()

join()方法是Thread类中的一个实例方法,常用于控制线程间的执行顺序,可以理解为线程插队,这个过程是同步的。join()方法的底层是基于wait()/notify来实现的,两参数方法的线程状态转化略有不同:

  • join():当前线程进入WAITING状态,等待方法调用线程执行结束后唤醒并进入RUNNABLE状态;
  • join(long millis):当前线程进入TIMED_WAITING状态,等待方法调用线程执行结束或超时时间到后唤醒并进入RUNNABLE状态;
public class methods_test {public static void main(String[] args) throws InterruptedException {Thread thread_1 = new Thread(() -> {try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("thread_1 run...");});Thread thread_2 = new Thread(() -> {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("thread_2 run...");});long start = System.currentTimeMillis();thread_1.start();thread_2.start();// 1. main thread wait thread_2 for 1000 millisthread_2.join();// 2. main thread wait thread_1 for 1000 millis(thread_1已经执行了1000millis)thread_1.join();long end = System.currentTimeMillis();System.out.println("cost time = " + (end - start)); // cost=2000}
}

2.3 yield方法

TypeMethodDescription
static voidyield()向调度器提示当前线程愿意放弃其当前对CPU处理器的占用,但调度器可以自由忽略此请求

yield()方法是Thread类中的一个静态本地Native方法,用于告诉线程调度器当前线程愿意放弃对CPU资源的占用,以倾向于使其他同优先级或更高优先级的线程可以调度运行。该方法的特点如下:

  • 不确定性: 调用了yield()方法并不意味着当前线程一定会让出CPU或者其他线程一定会获得执行机会,具体要看不同的JVM和操作系统对该方法的实现与实际调度情况;
  • 临时性: yield()方法只是有机会临时地放弃当前线程的执行权,但它不会改变线程的优先级或其他属性;
  • 不改变线程状态: 方法调用前后,Java中的线程状态(Thread.State)保持RUNNABLE不变;但在操作系统层面,线程的状态可能会从运行状态变为就绪状态
public class methods_test {public static void main(String[] args) {System.out.println("main start..." + Thread.currentThread().getName());Thread thread = new Thread(() -> {long start = System.currentTimeMillis();long sum = 0;for(long i = 0; i < 1000000; i++){sum += i;Thread.yield(); //添加运行time=356,不添加运行time=2}long end = System.currentTimeMillis();System.out.println("计算时间..." + Thread.currentThread().getName() + " time: " + (end - start) + " sum=" + sum);});thread.start();System.out.println("main end..." + Thread.currentThread().getName());}
}

2.4 interrupt方法

(1)方法介绍

TypeMethodDescription
voidinterrupt()中断当前线程,设置线程的中断标志位为true
booleanisInterrupted()获取当前线程的中断标志位,判断此线程是否被中断
static booleaninterrupted()获取当前线程的中断标志位,然后清除中断状态(将中断标志位设置为false)

线程中断是一种线程间的协作机制。每个Java线程都有一个内部中断标志位(boolean类型的信号量,默认初始为false),用于表明该线程是否被中断;interrupt()相关方法也并不直接控制线程的运行,而是通过修改中断标志位来传递中断信号,但当前线程具体是否响应中断(或是否停止执行)取决于它当前执行的操作和代码逻辑。实际上,Thread类中并没有维护线程的中断状态,而是操作系统层面对线程的中断标志,这些方法本质上是调用本地Native方法实现的。

  • 中断正常线程: 正常线程下调用 interrupt() 将只会将线程的中断标志设置为true,然后由被中断线程监控中断状态并自行处理中断逻辑,线程结束后会重置线程中断状态位注意中断后直到线程执行到wait()、sleep()、join()方法时也会直接抛出InterruptedException异常而不会进行阻塞
  • 中断阻塞线程(sleep、join、wait): 阻塞线程下这三种方法会持续监控中断状态,此时调用 interrupt() 会抛出一个 InterruptedException中断异常(需要提前catch异常),从而中断阻塞状态结束线程执行,使线程的状态直接到TERMINATED,同时中断标志会被清除(重置为false)。注意 sleep() / wait() / join() 调用后一定会消耗掉中断状态(中断状态最后一定是false),无论interrupt操作在方法之前还是之后;
  • 特殊场景: LockSupport.park也会响应中断结束阻塞(会继续向下执行),但是不会抛出异常且不会清空中断标记(仍为true,但线程执行结束后会重置为false),注意若中断标记为true则park方法也不会进行阻塞;除此之外,某些I/O操作(Socket、Selector)、sychronized加锁等阻塞场景则不会响应interrupt中断信号(方法本身不会检查中断状态);
//1.正常线程
public class methods_test {public static void main(String[] args) throws InterruptedException {System.out.println("main start..." + Thread.currentThread().getName());Thread thread = new Thread(() -> {boolean flag = false;for(long i = 0; i < 10000000000L; i++){if(!flag && Thread.currentThread().isInterrupted()){System.out.println("thread is interrupted...");flag = true;}//System.out.println("thread interrupt state = " + Thread.currentThread().isInterrupted());}System.out.println("thread running end...");});//before startSystem.out.println("before start thread state = " + thread.isInterrupted());//after startthread.start();Thread.sleep(10);System.out.println("after start thread state = " + thread.isInterrupted());//interruptthread.interrupt();Thread.sleep(10);System.out.println("first interrupt thread state = " + thread.isInterrupted());//interrupt againthread.interrupt();Thread.sleep(10);System.out.println("second interrupt thread state = " + thread.isInterrupted());//running endthread.join();Thread.sleep(1000);System.out.println("over running thread state = " + thread.isInterrupted());System.out.println("main end..." + Thread.currentThread().getName());}
}

main start…main
before start thread state = false
after start thread state = false
thread is interrupted…
first interrupt thread state = true
second interrupt thread state = true
thread running end…
over running thread state = false
main end…main

//2.阻塞线程
public class methods_test {public static void main(String[] args) throws InterruptedException {System.out.println("main start..." + Thread.currentThread().getName());Thread thread = new Thread(() -> {boolean flag = false;for(long i = 0; i < 10000000000L; i++){if(!flag && Thread.currentThread().isInterrupted()){System.out.println("thread is interrupted...");flag = true;}}System.out.println("thread loop end...");//[sleep] will running after thread [interrupt]try {System.out.println("thread state = " + Thread.currentThread().isInterrupted());//继续执行阻塞Thread.sleep(1000);System.out.println("thread sleep end and state = " + Thread.currentThread().isInterrupted());} catch (InterruptedException e) {//已经清除打断标记 = falseSystem.out.println("thread state = " + Thread.currentThread().isInterrupted());//直接不执行阻塞e.printStackTrace();System.out.println("thread sleep InterruptedException and state = " + Thread.currentThread().isInterrupted());}});//startthread.start();Thread.sleep(10); //调用本地native方法start0,可能不会立马改变线程状态System.out.println("after start thread state = " + thread.isInterrupted());//interruptthread.interrupt();Thread.sleep(10); //调用本地native方法interrupt0,可能不会立马改变线程状态System.out.println("first interrupt thread state = " + thread.isInterrupted());//wait jointhread.join();Thread.sleep(1000);System.out.println("over running thread state = " + thread.isInterrupted());System.out.println("main end..." + Thread.currentThread().getName());}
}

main start…main
after start thread state = false
thread is interrupted…
first interrupt thread state = true
thread loop end…
thread state = true
thread state = false
thread sleep InterruptedException and state = false
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.study.methods_test.lambda$main$0(methods_test.java:20)
at java.lang.Thread.run(Thread.java:748)
over running thread state = false
main end…main

(2)两阶段终止模式

两阶段终止模式是一种利用interrupt优雅停止线程工作的方式,并可以在线程停止时执行料理后事(释放资源、保存数据等)的逻辑。例如现有监控线程正在进行持续监控工作,我们可以通过两阶段终止模式在主线程终止监控线程执行,其代码如下:

class TwoPhaseTerminationTask {private Thread monitor;// 启动监控线程public void start() {monitor = new Thread(() -> {while (true) {Thread curr = Thread.currentThread();// 若当前线程处于中断状态,则执行料理后事逻辑if(curr.isInterrupted()) {System.out.println("thread interrupt 料理后事...");//释放资源、保存数据等...break;}try {// 执行监控任务System.out.println("thread monitor task...");// 避免死循环空转CPUThread.sleep(1000); //若在此处调用中断方法,则会触发InterruptedException终止try中的监控流程} catch (InterruptedException e) {// 处理异常e.printStackTrace();// 异常时中断位被清除为false, catch后会重新进入循环,需要调用中断方法重新改为true,以执行料理后事逻辑curr.interrupt();}}System.out.println("thread end...");}, "监控线程");monitor.start();}// 终止线程public void stop() {monitor.interrupt();}
}public class test_02 {public static void main(String[] args) throws InterruptedException {TwoPhaseTerminationTask monitorTask = new TwoPhaseTerminationTask();monitorTask.start();Thread.sleep(5000);monitorTask.stop();}
}

2.5 LockSupport工具类

(1)方法介绍

LockSupport类是java.util.concurrent.locks包下的基本阻塞工具类,用于控制线程的阻塞与唤醒(原语),在功能上类似于wait/notify;其常用方法如下:

TypeMethodDescription
static voidpark()阻塞当前线程执行(线程状态进入WAITING),直到被唤醒或中断
static voidparkNanos(long nanos)阻塞当前线程执行,最多阻塞nanos纳秒(线程状态进入TIMED_WAITING),直到到期、被唤醒或中断
static void unpark(Thread thread)唤醒通过park()方法阻塞的线程thread(线程状态恢复RUNNABLE),若给定线程尚未启动start则操作可能失效

简单来说,LockSupport类是基于一种permit(许可)的二元信号量来实现的阻塞/唤醒机制,每个Java线程在底层都会绑定一个许可信号量,该信号量只有有和无两种状态(1和0,默认为0表示没有许可);当线程调用park方法时会去请求获取许可,如果能够成功获取到许可则程序继续执行(消耗许可信号,并将状态置为0),否则将阻塞等待直到获取许可;而当线程调用unpark方法时则会将许可置为可用(发放许可即将状态置为1,但许可不可累加),以供线程获取。因此,LockSupport 类相关方法的特点如下:

  • 比较wait/notify: LockSupport 方法不需要在同步代码块中使用(可以在任意地方调用),并且能够精确唤醒某个线程;
  • 顺序无关: park/unpark方法没有执行顺序要求。若先调用unpark方法再调用park方法则程序会直接跳过park阻塞继续执行,多个unpark方法可以连续使用(许可不会累加),多个park方法也可以连续使用但仅有首个park方法不会阻塞(park会消耗许可并将状态置为0);
  • 不释放锁: park方法不会释放线程所持有的锁资源(比如synchronized),它只负责阻塞线程;
  • 中断响应: interrupt方法可以中断LockSupport.park方法的阻塞(park方法内部实现了先对中断状态的检查),并且不会抛出异常也不会在方法结束前清除中断标记(true),但unpark不会受到中断的影响;
public class test_03 {public static void main(String[] args) throws InterruptedException {System.out.println("start...");Thread worker = new Thread(() -> {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}// 第一个park: 被1.unpark唤醒,消耗许可System.out.println("线程开始第一次park");LockSupport.park();System.out.println("线程第一次park结束");// 第二个park: 被2.unpark唤醒,消耗许可System.out.println("线程开始第二次park");LockSupport.park();System.out.println("线程第二次park结束");// 第三个park: 被3.interrupt中断System.out.println("线程开始第三次park");LockSupport.park();System.out.println("线程第三次park结束");// 第四个park: 不清除中断标记,直接跳过park不阻塞System.out.println("线程开始第四次park");LockSupport.park();System.out.println("线程第四次park结束");System.out.println("--------------------");// 清除中断标记System.out.println("中断标记(清除前): " + Thread.interrupted());System.out.println("中断标记(清除后): " + Thread.currentThread().isInterrupted());// 再次测试park: 阻塞System.out.println("线程开始第五次park");LockSupport.park();System.out.println("线程第五次park结束");});worker.start();Thread.sleep(10);//1.测试先后顺序+许可不累加: unpark不影响sleepLockSupport.unpark(worker);LockSupport.unpark(worker);LockSupport.unpark(worker);//2.测试正常唤醒Thread.sleep(5000);LockSupport.unpark(worker);//3.测试中断parkThread.sleep(5000);worker.interrupt();worker.join();System.out.println("end...");}
}

(2)原理分析

LockSupport 的 park/unpark 最终调用还是 Unsafe 类实现,而 Unsafe 类的 park/unpark 方法是本地 native 级别的实现,属于 C/C++ 语言实现的原生函数。每个 Java 线程都绑定了一个 Parker 实例,该实例包含了 park/unpark 方法实现的核心属性和逻辑;在 Linux 系统下,park 和 unpark 是用 Posix 线程库 pthread 中的 mutex(互斥锁)、condition(条件变量) 来实现的。简单来说,mutex 和 condition 保护了一个叫 _counter 的信号量,这个信号量就是上述的许可permit调用 park 时,这个变量被设置为 0,当调用 unpark 时,这个变量被设置为1。在判断时,当 _counter=0 时线程阻塞,当 _counter > 0 则直接设为 0 并返回(不阻塞)。具体的阻塞和唤醒实现则是通过了 pthread_cond_waitpthread_cond_signal 两个库函数。其关系如下:

在这里插入图片描述

  • 线程调用Unsafe.park()方法;
  • 检查信号量_counter是否大于0,如果是则设为0并返回;
  • 检查中断状态是否已经interrupt了(不清除标记),如果是则立刻返回不阻塞;
  • 若信号量_counter不大于0,则获得_mutex互斥锁;
  • 当前线程进入_cond条件变量阻塞,等待信号唤醒;
  • 设置_counter = 0;

三. Callable与FutrueTask

1. Callable

Callable接口与Runnable接口类似,也是线程体的一种承载方式,其接口定义源码如下:

@FunctionalInterface
public interface Callable<V> {/*** Computes a result, or throws an exception if unable to do so.** @return computed result* @throws Exception if unable to compute a result*/V call() throws Exception;
}

由上可知,Callable是一个函数式接口,且其只有一个接口方法call(),用于封装待实现的任务执行逻辑;但与Runnable不同的是:

  • 有返回值:Callable是泛型接口,call()方法可以返回执行结果;
  • 可以抛出异常: call()方法可以在执行过程中抛出受检查的异常;

Callable的执行主要有两种方式,一种是通过线程池的ExecutorService.submit(Callable<T> task)方法提交任务(将在线程池部分进行讲解);另一种则是通过Thread进行启动,但是Thread类并没有直接提供用于包装Callable线程体的初始化方法,这就需要配合FutureTask来进行实现(详见下第2节)。

2. FutrueTask

在这里插入图片描述

由继承关系结构可知,FutureTask泛型类实现了RunnableFuture泛型接口,而RunnableFuture接口又分别实现了Runnable接口和Future接口。其中,Future接口用于表示异步计算的结果,并提供了一系列检查、等待、取消计算的方法,但它不提供计算逻辑的封装;而Runnable接口则提供了可被Thread执行启动的线程体,但run()方法没有返回值。FutureTask类同时实现了上述两个接口,并且通常接受一个Callable实现传入构造参数以作为线程体(Callablecall方法有返回值),因此FutureTask拥有三者的全部特性,它既可以作为可获取返回值的线程体执行,也可以表示异步计算的返回值结果(存储了 Callablecall 方法的任务执行结果)Callable的执行通常也需要与FutureTask配合启动。其常用方法如下:

TypeMethodDescriptionTips
booleancancel(boolean mayInterruptIfRunning)尝试取消任务的执行。如果任务已完成、已取消或其他原因取消失败,则返回false;如果任务成功取消,则返回true注意若调用取消时此任务尚未启动,则此任务永不运行;若任务已启动,则根据参数确定是否应中断执行的任务线程。参数mayInterruptIfRunning表示是否中断正在执行的任务。如果true则会中断正在执行的任务;如果false则不会中断正在执行的任务(允许当前任务执行完成)。
Vget() 获取任务的执行结果。如果任务还未完成,该方法会阻塞当前线程,直到任务完成并返回。如果任务已取消,则该方法会抛出CancellationException异常;如果任务执行过程中抛出了异常,则该方法会抛出ExecutionException异常,并将原始异常作为ExecutionException的cause;如果当前线程在等待时被中断,则该方法处会抛出InterruptedException异常。
Vget(long timeout, TimeUnit unit)在指定的超时时间timeout内阻塞等待获取任务的执行结果。如果任务在指定时间内完成,则返回结果;如果超时仍未完成,则会抛出TimeoutException异常(但不会终止原任务线程的执行)。如果任务已取消,则该方法会抛出CancellationException异常;如果任务执行过程中抛出了异常,则该方法会抛出ExecutionException异常,并将原始异常作为ExecutionException的cause;如果当前线程在等待时被中断,则该方法处会抛出InterruptedException异常。
booleanisCancelled()判断任务在正常完成之前是否已被取消。如果任务已经被取消,则返回true;如果任务还未被取消,则返回false/
booleanisDone()判断任务是否已经完成。如果任务已经完成(包括正常完成、被取消或执行过程中抛出异常结束),则返回true;如果任务还未完成,则返回false/
voidrun()执行任务逻辑(call方法),设置并存储执行结果。/
public class test_03 {public static void main(String[] args) {FutureTask<Integer> task = new FutureTask<>(new Callable<Integer>() {@Overridepublic Integer call() throws Exception {System.out.println("[worker] task start...");Integer flag = 0;for(long i = 0; i < 10000000000L; i++){// 测试 cancel 方法触发中断但不终止线程if(flag == 0 && Thread.currentThread().isInterrupted()){System.out.println("[worker] thread is canceled...");flag = 100;}}System.out.println("[worker] task end...");return flag;}});// callable worker threadThread worker = new Thread(task);// interruptor threadThread interruptor = new Thread(new Runnable() {@Overridepublic void run() {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}//中断worker线程任务执行: 实现方式为发出中断信号task.cancel(true);System.out.println("[interruptor] cancel worker is done...");}});// start threadworker.start();interruptor.start();// wait for gettry {// main thread wait for task...Integer res = task.get();System.out.println("[main] res = " + res);} catch (InterruptedException e) {e.printStackTrace();} catch (ExecutionException e) {e.printStackTrace();}catch (CancellationException e){// cancel 后 get 抛出 CancellationExceptione.printStackTrace();}System.out.println("[main] thread end...");}
}

[worker] task start…
[interruptor] cancel worker is done…
[worker] thread is canceled…
[main] thread end…
java.util.concurrent.CancellationException
at java.util.concurrent.FutureTask.report(FutureTask.java:121)
at java.util.concurrent.FutureTask.get(FutureTask.java:192)
at com.study.test_03.main(test_03.java:51)
[worker] task end…

3. FutrueTask源码分析

参考文章:

(1) [(八)深入并发之Runnable、Callable、FutureTask原理分析-竹子爱熊猫]

(2) [FutureTask源码解析-chenzeyang]

3.1 任务状态

FutureTask中,其内部通过一个用volatile关键字修饰的int变量state来对任务的执行状态进行表示和管理。其源码如下:

    /** Possible state transitions:* NEW -> COMPLETING -> NORMAL* NEW -> COMPLETING -> EXCEPTIONAL* NEW -> CANCELLED* NEW -> INTERRUPTING -> INTERRUPTED*/private volatile int state;private static final int NEW          = 0;private static final int COMPLETING   = 1;private static final int NORMAL       = 2;private static final int EXCEPTIONAL  = 3;private static final int CANCELLED    = 4;private static final int INTERRUPTING = 5;private static final int INTERRUPTED  = 6;

状态变量 state 存在四种状态转换变化(参考注释):

  • NEW:初始化状态,任务刚被创建出来处于的状态;
  • COMPLETING:终止中间状态,任务从NEW变为NORMAL/EXCEPTIONAL时会经历的短暂状态;
  • NORMAL:正常终止状态,任务正常执行完成后处于的状态;
  • EXCEPTIONAL:异常终止状态,任务执行过程中抛出了异常后处于的状态;
  • CANCELLED:取消状态,任务被成功取消后处于的状态;
  • INTERRUPTING:中断中间状态,任务还未执行或在执行中时调用cancel(true)处于的中间状态;
  • INTERRUPTED:中断最终状态,执行任务被取消且执行线程被中断后处于的状态;

如上便是FutureTask在不同情况下会经历的所有状态。当创建一个FutureTask时,FutureTask对象的state一定是处于NEW新建状态的(因为在FutureTask的构造方法中会执行this.state = NEW)。同时随着任务开始执行之后,FutureTask的状态会开始发生转换,在NEW→NORMALNEW→EXCEPTIONAL两种状态转换过程中,还会出现COMPLETING中间状态,但这种中间状态存在的时间非常短暂(注意中间态并不代表任务正在执行中,而是任务已执行完毕且正在设置返回结果),也会马上变成相应的最终状态。不过值得我们注意的是:FutureTask的状态转换是不可逆的,并且同时只要状态不处于NEW初始化状态,那么就可以认为该任务已经结束

3.2 成员结构

FutureTask的核心成员结构的源码如下:

// 任务的执行状态
private volatile int state;
// 异步任务:Callable对象
private Callable<V> callable;
// 任务执行结果(因为是Object类型,所以异常也是可以保存的)
private Object outcome;
// 执行者线程
private volatile Thread runner;
// 等待者线程:由WaitNode内部类构成的链表
private volatile WaitNode waiters;// 静态内部类:WaitNode
static final class WaitNode {volatile Thread thread;volatile WaitNode next;WaitNode() { thread = Thread.currentThread(); }
}

由上可知,在FutureTask中主要存在两类线程

  • 执行线程:执行异步任务callable的线程,只存在一条线程;
  • 等待线程:等待获取执行结果的线程(如调用了get阻塞),可能存在多条线程;

Future接口的get()方法作用是获取异步执行之后的结果,若执行还未完成,获取执行结果的等待者则会阻塞。FutureTask对该方法进行了实现,其内部在逻辑上存在一个由WaitNode节点组成的单向链表,当一条线程尝试获取执行结果但是还未执行结束时,FutureTask则会将每个等待者封装成一个WaitNode节点,然后将其加入该链表中,并通过头节点head持有该阻塞链表,直至执行者的任务执行完成后,再唤醒链表的每个节点中的线程

3.3 run方法实现

作为线程体实现的核心方法,我们先看看FutureTask中的run()方法的实现逻辑,其源码分析如下:

public void run() {// 1.判断state是否为NEW,如果不是代表任务已经执行过或被取消// 2.判断执行者位置上是否有线程,有则代表着当前任务正在执行// 如果state不为NEW或者执行者不为空都会直接终止当前线程执行if (state != NEW ||!UNSAFE.compareAndSwapObject(this, runnerOffset,null, Thread.currentThread()))return;// 3.如果state==NEW且runner==currentThread,则继续往下执行try {// 获取需要执行的异步任务Callable<V> c = callable;// 检查任务是否为空并再次检查state是否处于初始化状态if (c != null && state == NEW) {// 接收结果V result; // 接收终止状态:true为正常结束,false则为异常结束boolean ran; try {// 调用call()执行任务并获取执行结果result = c.call();// 终止状态改为正常结束ran = true;} catch (Throwable ex) {// 返回结果置为空result = null;// 终止状态改为异常结束ran = false;// CAS-将捕获的异常设置给outcome全局变量setException(ex);}// 如果执行状态为正常结束if (ran)// CAS-将执行结果设置给outcome全局变量set(result);}} finally {// 将执行者线程的引用置为nullrunner = null;// 检查state是否为INTERRUPTING、INTERRUPTED中断状态int s = state;if (s >= INTERRUPTING)// 如果是则响应线程中断handlePossibleCancellationInterrupt(s);}
}

如上便是FutureTaskrun()方法的实现。简单来说,在FutureTask.run()方法中主要做了如下四步:

  • 判断任务执行状态,如果正在执行或被执行过则直接return,反之则继续执行任务;
  • 如果任务执行过程中出现异常,则调用setException()写出捕获的异常信息;
  • 如果任务执行成功后,获取执行返回值并调用set()写出任务执行完成后的返回值;
  • 任务执行结束时,判断任务状态是否需要中断,需要则调用handlePossibleCancellationInterrupt()进行中断处理;

接下来再详细看看setException()set()方法的源码实现分析:

//setException()方法
protected void setException(Throwable t) {// 利用CAS机制将state改为COMPLETING中间状态if (UNSAFE.compareAndSwapInt(this,stateOffset,NEW,COMPLETING)) {// 将捕获的异常写出给outcome成员outcome = t;// 再次利用CAS修改state为EXCEPTIONAL异常终止状态UNSAFE.putOrderedInt(this,stateOffset,EXCEPTIONAL); // 最终态// 唤醒等待队列中的等待者线程finishCompletion();}
}//set()方法
protected void set(V v) {// 利用CAS机制修改state为COMPLETING中间状态if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {// 将执行结果写出给outcome成员outcome = v;// 再次利用CAS修改state为NORMAL正常终止状态UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // 最终态// 唤醒等待队列中的等待者线程finishCompletion();}
}

如源码所示,当任务执行过程中抛出异常时会调用setException()方法而该方法的逻辑也分为四步:

  • 先使用CAS操作将state修改为COMPLETING中间状态;
  • 将捕获的异常写出给outcome成员;
  • 写出捕获的异常后再次使用CAS将state改为EXCEPTIONAL异常终止状态;
  • 调用finishCompletion()方法唤醒等待列表中的等待者线程;

不过当任务正常执行结束后会调用set()方法,而set()方法与setException()方法同理,也分为四步执行:

  • 先使用CAS操作将state修改为COMPLETING中间状态;
  • 将任务正常执行结束的返回值写出给outcome成员;
  • 写出后再次使用CAS将state改为NORMAL正常终止状态;
  • 调用finishCompletion()方法唤醒等待列表中的等待者线程;

紧接着再来看看唤醒等待链表中“等待者”线程的finishCompletion()方法:finishCompletion()方法的逻辑相对来说就比较简单了,即通过成员变量waiters中保存的head头节点与next后继节点指针,遍历唤醒整个逻辑链表所有节点中的等待者线程。

//finishCompletion()方法
private void finishCompletion() {
// 该方法调用前state一定要为最终态// 获取waiters中保存的head节点,根据head遍历整个逻辑链表for (WaitNode q; (q = waiters) != null;) {// 利用cas操作将原本的head节点置nullif (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {for (;;) {// 获取q节点的线程Thread t = q.thread;if (t != null) {q.thread = null; // 置空线程引用信息LockSupport.unpark(t);// 唤醒节点中的线程}// 获取链表中的下一个节点WaitNode next = q.next;// 如果下一个节点为空则代表着已经是链表尾部了if (next == null)// 那么则终止循环break;// 置空当前节点的后继节点引用信息方便GCq.next = null; // 将获取到的后继节点赋值给qq = next;}// 当遍历完成整个链表后退出循环break;}}// 注意:done()方法没有具体实现,留给使用者自身用于拓展的// 可以根据需求让执行者线程在执行完毕前多做一点善后工作done();callable = null;        // to reduce footprint
}

最后,在finally语句块中,会判断state是否为为INTERRUPTINGINTERRUPTED中断状态,如果是则会调用handlePossibleCancellationInterrupt()方法响应线程中断操作,其源码如下:

finally {// 将执行者线程的引用置为nullrunner = null;// 检查state是否为INTERRUPTING、INTERRUPTED中断状态int s = state;if (s >= INTERRUPTING)// 如果是则响应线程中断handlePossibleCancellationInterrupt(s);
}//handlePossibleCancellationInterrupt()方法
private void handlePossibleCancellationInterrupt(int s) {// 1.如果state==INTERRUPTING中断中间状态if (s == INTERRUPTING)// 3.如果线程再次从就绪状态获取到cpu资源回到执行状态//   循环调用yield()方法让当前线程持续处于就绪状态//   直至线程被中断且state==INTERRUPTED为止while (state == INTERRUPTING)// 2.调用yield()让当前线程让出cpu资源退出执行状态//   回到就绪状态方便响应线程中断Thread.yield(); 
}

3.4 接口方法实现

(1)get方法

//get()方法
public V get() throws InterruptedException, ExecutionException {// 获取任务状态int s = state;// 如果任务状态不大于终止中间状态则阻塞线程if (s <= COMPLETING)s = awaitDone(false, 0L);// 如果state大于终止中间状态则代表是最终态了,则返回执行结果return report(s);
}//超时版get()方法
public V get(long timeout, TimeUnit unit)throws InterruptedException, ExecutionException, TimeoutException {if (unit == null)throw new NullPointerException();int s = state;// 如果任务还没执行完成,那么调用awaitDone传递给定的时间阻塞线程// 如果时间到了状态还是不大于COMPLETING则代表任务还未执行结束// 那么抛出TimeoutException强制中断当前方法执行,退出等待状态if (s <= COMPLETING &&(s = awaitDone(true, unit.toNanos(timeout))) <= COMPLETING)throw new TimeoutException();// 如果任务执行完成或在给定时间内执行完成则返回执行结果return report();
}

FutureTask.get()方法的逻辑相对来说就比较简单了:

  • 首先判断了一下任务有没有执行结束(state是否<=COMPLETING,如果<=代表还没结束);
  • 如果任务还未执行或还在执行过程中,调用awaitDone()方法阻塞当前等待者线程;
  • 如果任务已经执行结束(状态变为了最终态),调用report()返回执行的结果;

而超时等待版的FutureTask.get()方法则是在第二步的时候有些不一致,在超时版本的get方法中,当任务还未执行或者还在执行过程中的情况下,会调用awaitDone(true,unit.toNanos(timeout)))方法在给定的时间内等待,如果时间到了任务还未执行结束则会抛出TimeoutException异常强制线程退出等待状态。接着再来看看awaitDone()report()方法:

//awaitDone()方法
private int awaitDone(boolean timed, long nanos)throws InterruptedException {// 计算等待的截止时间final long deadline = timed ? System.nanoTime() + nanos : 0L;WaitNode q = null;boolean queued = false;//注意这里this是当前阻塞等待get的线程,而非runner线程!!for (;;) {// 如果出现线程中断信息则移除等待链表节点信息if (Thread.interrupted()) {removeWaiter(q);throw new InterruptedException();}// 再次获取任务状态,如果大于COMPLETING// 代表任务已经执行结束,直接返回最新的state值int s = state;if (s > COMPLETING) {if (q != null)q.thread = null;return s;}// 如果state==中间状态代表任务已经快执行完成了// 那么则让当前线程让出cpu资源进入就绪状态稍微等待else if (s == COMPLETING) // cannot time out yetThread.yield();// 如果任务还在执行或还未执行则构建waitnode节点else if (q == null)q = new WaitNode();// 利用cas机制将构建好的waitnode节点加入逻辑链表// 注意:该链表是栈的结构,所以并不是将新的节点// 变为之前节点的next,而是新节点变为head节点// 旧节点变为next后继节点(这样方便维护逻辑链表结构)else if (!queued)queued = UNSAFE.compareAndSwapObject(this, waitersOffset,q.next = waiters, q);// 如果是超时等待的模式,则先判断时间有没有超时// 如果已经超时则删除对应节点并返回最新的state值else if (timed) {nanos = deadline - System.nanoTime();if (nanos <= 0L) {removeWaiter(q);return state;}// 如果还没超时则将线程挂起阻塞指定时间LockSupport.parkNanos(this, nanos);}else// 如果不是超时版本则直接挂起阻塞线程LockSupport.park(this);}
}//report()方法
private V report(int s) throws ExecutionException {// 获取成员变量outcome的值Object x = outcome;// 如果state为正常终止状态则返回执行结果if (s == NORMAL)return (V)x;// 如果任务被取消或线程被中断则抛出CancellationException异常if (s >= CANCELLED)throw new CancellationException();// 如果state为异常终止状态则抛出捕获的异常信息throw new ExecutionException((Throwable)x);
}

源码如上,先来看看略微复杂的awaitDone()方法,在awaitDone()方法首先会计算等待的截止时间(如果不需要超时等待则没有截止时间),计算好等待的截止时间之后会开启一个死循环,在循环中每次都会执行如下步骤:

  • 判断当前等待者线程是否被其他线程中断,如果是则移除对应节点信息并抛出InterruptedException异常强制中断执行;
  • 判断state是否大于COMPLETING,大于则代表任务执行结束,置空当前节点线程信息并返回最新state值;
  • 判断state是否等于COMPLETING,等于则代表执行即将结束,当前线程让出CPU资源,退出执行状态并进入就绪状态,等待最终态的出现;
  • 如果任务还未执行结束,则判断当前等待者线程是否已经构建waitnode节点,如果没有则为当前线程构建节点信息;
  • 如果新构建出的节点还未加入等待链表,则利用cas机制将构建的节点设置为head节点,老head变为当前节点的next节点;
  • 使用LockSupport类将当前线程挂起阻塞,如果是超时等待的get()则挂起阻塞特定时间;

(2)cancel方法

//cancel()方法
// - mayInterruptIfRunning=true: 代表中断执行者线程执行
// - mayInterruptIfRunning=false: 代表取消任务执行
public boolean cancel(boolean mayInterruptIfRunning) {// 如果state!=NEW代表任务已经执行结束,直接返回false// 如果入参为false则CAS修改state=CANCELLED,返回true即可if (!(state == NEW &&UNSAFE.compareAndSwapInt(this, stateOffset, NEW,mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))return false;// 因为call()方法执行过程中可能会抛出异常// 所以需要try finally语句块保证等待者线程唤醒try {// 如果入参为true,则中断执行者线程if (mayInterruptIfRunning) {try {Thread t = runner;if (t != null)// interrupt()不能保证一定中断执行线程// 因为强制中断线程不安全,所以java弃用了stop()方法// 而是换成了协调式中断,线程调用interrupt()后// 只会发出中断信号,由被中断线程决定响不响应中断操作t.interrupt();} finally { // final state// 中断后利用CAS修改state=INTERRUPTED最终态UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);}}} finally {// 不管是取消任务还是中断执行,完成后都会唤醒等待链表的所有线程finishCompletion();}return true;
}

当调用FutureTask.cancel()方法后,会根据传入的boolean值决定是进行中断执行者线程操作还是取消任务执行,具体执行逻辑如下:

  • mayInterruptIfRunning传入true的情况下:

    • 判断任务是否已经执行结束,是则直接返回false,反之则利用cas机制将state=INTERRUPTING中断中间状态;
    • 获取执行者线程runner成员,调用执行者的interrupt()方法向执行者线程发出一个中断信号;
    • 利用CAS机制将state=INTERRUPTED中断最终态;
    • 调用finishCompletion()方法唤醒链表中的所有等待者线程;
  • mayInterruptIfRunning传入false的情况下:

    • 判断任务是否已经执行结束,是则直接返回false
    • 如果任务还未执行或还在执行则利用cas机制将state=CANCELLED取消状态;

但是需要注意的是,t.interrupt()方法是无法强制中断线程执行的,具体原因并不是因为无法做到强制中断,这个是可以实现的,在JDK的早期版本中Thread也提供了stop()方法可以用来强制停止执行线程,但是因为强制关闭执行线程会导致一系列的问题产生,如:安全性问题、数据一致性问题等,所以在JDK后续的版本中便弃用了stop()这类强制式中断线程的方法,而是换成interrupt()这种协调式中断的方法。当一条线程调用另外一条线程的interrupt()方法后并不会直接强制中断线程,而是仅仅给需要中断的线程发送一个中断信号,而是否中断执行则是由执行线程本身决定的,而关于执行线程是否能检测到这个发出的中断信号则需要取决于执行线程运行的代码。

​ 到这里有人可能会疑惑:不能保证一定中断执行者线程,那么这个cancel()方法还有作用吗?答案是肯定有用的,因为调用cancel()方法之后,只要任务没有执行结束的情况下,state一定会被修改成CANCELLED 或者INTERRUPTING状态,那么后续等待者线程在执行get时则会检测到任务状态已经不是NEW了,那么则不会awaitDone()方法阻塞等待了;或者是get方法阻塞中时(等待线程),被其他线程通过finishCompletion唤醒,并继续从awaitDoneLockSupport.park方法处继续向下执行返回,并通过report方法获取结果抛出异常注意这里执行get逻辑的是当前等待结果的线程,而非runner线程

(3)isCancelled、isDone方法

这是两个is判断类型的方法:isCancelled()、isDone()源码分析如下:

//isCancelled()方法
public boolean isCancelled() {// 如果state>=CANCELLED都代表状态不是执行终止状态// 那么则代表着肯定是中断或者取消状态,所以:// state>=CANCELLED代表任务被取消,反之则是没有return state >= CANCELLED;
}//isDone()方法
public boolean isDone() {// 如果state!=NEW初始化状态则代表任务执行结束// 因为就算是中间状态也会很快变为最终态return state != NEW;
}

四. 线程生命周期

4.1 线程状态源码

    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;}

4.2 生命周期转化

由上述线程状态的源码定义可知,Java 线程在运行的生命周期中的指定时刻下只可能处于以下 6 种不同线程状态的其中一个状态阶段:

  • NEW: 初始状态,线程被创建出来但没有被调用 start()
  • RUNNABLE: 运行状态,线程被调用了 start()启动后的等待和运行状态;
  • BLOCKED: 阻塞状态,需要等待锁释放;
  • WAITING: 等待状态,表示该线程需要等待其他线程做出一些特定动作(通知或中断);
  • TIME_WAITING: 超时等待状态,可以等待通知或在指定的时间后自行返回而不是像 WAITING 一直等待;
  • TERMINATED: 终止状态,表示该线程已经运行完毕;

线程在生命周期中并不是固定处于某个状态而是随着代码的执行在不同状态之间切换。注意在操作系统层面,线程有 READY 和 RUNNING 两种状态区分;而在 JVM 层面,则只能看到 RUNNABLE 状态(由于状态切换很快故不再区分),所以 Java 系统一般将这两个状态统称为 RUNNABLE(运行中) 状态 。Java 线程状态变迁图如下所示( 挑错 |《Java 并发编程的艺术》中关于线程状态的三处错误):
在这里插入图片描述

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

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

相关文章

深入剖析 MQTT 协议:物联网通信的核心力量

摘要&#xff1a; 本文全面深入地探讨了 MQTT&#xff08;Message Queuing Telemetry Transport&#xff09;协议。详细阐述了 MQTT 协议的起源与发展背景&#xff0c;介绍其基本概念、特点及工作原理。深入分析了 MQTT 的架构组成&#xff0c;包括客户端、代理服务器及主题的作…

Jenkins部署若依项目

一、配置环境 机器 jenkins机器 用途&#xff1a;自动化部署前端后端&#xff0c;前后端自动化构建需要配置发送SSH的秘钥和公钥&#xff0c;同时jenkins要有nodejs工具来进行前端打包&#xff0c;maven工具进行后端的打包。 gitlab机器 用途&#xff1a;远程代码仓库拉取和…

基于Linux的ARMxy工控机IEC61850协议实践

工业自动化水平的不断提高&#xff0c;对设备间高效、可靠通信的需求日益增长。IEC61850标准作为电力系统自动化领域的重要国际标准之一&#xff0c;其应用范围正在从传统的电力行业向更广泛的工业自动化领域扩展。本文将探讨基于ARM架构的工业计算机如何在Linux操作系统环境下…

解码未来:H.265与H.266技术对比及EasyCVR视频汇聚平台编码技术优势

随着视频技术的不断发展&#xff0c;视频编码标准也在不断更新迭代。H.265&#xff08;也称为HEVC&#xff0c;High Efficiency Video Coding&#xff09;和H.266&#xff08;也称为VVC&#xff0c;Versatile Video Coding&#xff09;作为当前和未来的主流视频编码标准&#x…

BrainSegFounder:迈向用于神经影像分割的3D基础模型|文献速递--Transformer架构在医学影像分析中的应用

Title 题目 BrainSegFounder: Towards 3D foundation models for neuroimagesegmentation BrainSegFounder&#xff1a;迈向用于神经影像分割的3D基础模型 01 文献速递介绍 人工智能&#xff08;AI&#xff09;与神经影像分析的融合&#xff0c;特别是多模态磁共振成像&am…

【机器学习】马尔可夫随机场的基本概念、和贝叶斯网络的联系与对比以及在python中的实例

引言 马尔可夫随机场&#xff08;Markov Random Field&#xff0c;简称MRF&#xff09;是一种用于描述变量之间依赖关系的概率模型&#xff0c;它在机器学习和图像处理等领域有着广泛的应用 文章目录 引言一、马尔科夫随机场1.1 定义1.2 特点1.3 应用1.4 学习算法1.5 总结 二、…

【数据分析预备】Pandas

Pandas 构建在NumPy之上&#xff0c;继承了NumPy高性能的数组计算功能&#xff0c;同时提供更多复杂精细的数据处理功能 安装 pip install pandas导入 import pandas as pdSeries 键值对列表 # 创建Series s1 pd.Series([5, 17, 3, 26, 31]) s10 5 1 17 2 3 3 26 4 31 dt…

Windows更新之后任务栏卡死?桌面不断闪屏刷新?

前言 小白这几天忙于工作&#xff0c;更新就变得异常缓慢。但就算这么忙的情况下&#xff0c;晚上休息的时间还是会给小伙伴们提供咨询和维修服务。 这不&#xff0c;就有一个小伙伴遇到了个很奇怪的问题&#xff1a;电脑Windows更新之后&#xff0c;任务栏点了没反应&#xf…

C++当中的多态(三)

&#xff08;六&#xff09;虚表的本质 其实我们大家应该都已经猜到了&#xff1a;我们虚表的本质就是一个函数指针数组。通过访问这个函数指针数组就可以得到我们想要的虚函数的地址&#xff0c;之后通过这个地址就可以调用我们相应的虚函数。我们这个函数指针数组是以nullptr…

如何使用python运行Flask开发框架并实现无公网IP远程访问

文章目录 1. 安装部署Flask2. 安装Cpolar内网穿透3. 配置Flask的web界面公网访问地址4. 公网远程访问Flask的web界面 本篇文章主要讲解如何在本地安装Flask&#xff0c;以及如何将其web界面发布到公网进行远程访问。 Flask是目前十分流行的web框架&#xff0c;采用Python编程语…

云服务器部署DB-GPT项目

本文收录于《DB-GPT项目》专栏&#xff0c;专栏总目录&#xff1a; 点击这里。 文章目录 项目介绍 一、登录云服务器 1. 进入控制台 2.点击容器实例&#xff08;点数字&#xff09; 二、创建容器实例 1. 等待容器实例创建好&#xff0c;创建好的容器实例如下&#xff1a;…

海康威视相机在QTcreate上的使用教程

文章目录 前言&#xff1a;基础夯实&#xff1a;效果展示&#xff1a;图片展示&#xff1a;视频展示&#xff1a; 参考的资料&#xff1a;遇到问题&#xff1a;问题1&#xff1a;int64 does not问题2&#xff1a;LNK2019配置思路(这个很重要)配置关键图片&#xff1a;配置具体过…

erlang学习: Mnesia Erlang数据库3

Mnesia数据库删除实现和事务处理 -module(test_mnesia). -include_lib("stdlib/include/qlc.hrl").-record(shop, {item, quantity, cost}). %% API -export([insert/3, select/0, select/1, delete/1, transaction/1,start/0, do_this_once/0]). start() ->mnes…

dp+观察,CF 1864 D. Matrix Cascade

目录 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 二、解题报告 1、思路分析 2、复杂度 3、代码详解 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 https://codeforces.com/problemset/problem/1864/D 二、解题报告 1、思路…

Python | Leetcode Python题解之第394题字符串解码

题目&#xff1a; 题解&#xff1a; class Solution:def decodeString(self, s: str) -> str:def dfs(s, i):res, multi "", 0while i < len(s):if 0 < s[i] < 9:multi multi * 10 int(s[i])elif s[i] [:i, tmp dfs(s, i 1)res multi * tmpmulti…

AI应用 | 超好玩的“汉语新解“ - 文末有Prompt

最近群里玩“汉语新解”的文字卡片贼多 感觉很新颖 本来AI是无法生成固定的图的 但是使用html格式&#xff0c;来生成固定图片的想法还是很不错的 看看效果 使用很简单 把提示词喂给Ai即可 随便一个大模型都可以&#xff0c;比如ChatGPT、通义千问、kimi等等 提示词(Prompt)如下…

基于SpringBoot+Vue+MySQL的房屋租赁管理系统

系统展示 用户前台界面 管理员后台界面 系统背景 二十一世纪互联网的出现&#xff0c;改变了几千年以来人们的生活&#xff0c;不仅仅是生活物资的丰富&#xff0c;还有精神层次的丰富。在互联网诞生之前&#xff0c;地域位置往往是人们思想上不可跨域的鸿沟&#xff0c;信息的…

基于SpringBoot+Vue的超市外卖管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、SSM项目源码 系统展示 【2025最新】基于JavaSpringBootVueMySQL的…

8.Bug流程管理,禅道的使用(包含笔试/面试题)

一、bug的生命周期&#xff08;重点&#xff09; bug的生命周期就是从bug被发现到bug被关闭的整个过程。 1.bug生命周期&#xff1a; 新建&#xff08;提交bug&#xff09; - 指派 - 已解决 - 待验 - 关闭 new&#xff08;新建&#xff09; - assign额的&…

Python语言开发学习之使用Python预测天气

什么是wttr&#xff1f; 使用Python预测天气的第一步&#xff0c;我们要了解wttr是什么。wttr.in是一个面向控制台的天气预报服务&#xff0c;它支持各种信息表示方法&#xff0c;如面向终端的ANSI序列(用于控制台HTTP客户端(curl、httpie或wget))、HTML(用于web浏览器)或PNG(…