Java Thread 介绍

news2025/1/11 17:52:50

线程是操作系统调度的最小单元, 也叫轻量级进程。它被包含在进程之中, 是进程中的实际运作单位。
同一进程可以创建多个线程, 每个线程都有自己独立的一块内存空间, 并且能够访问共享的内存变量。

1 线程的分类

在 Java 中, 线程可以分为 2 种

  1. 守护线程: 守护线程是为用户线程服务的线程, 在后台默默地完成一些系统性的服务, 如垃圾回收等
  2. 用户线程: 真正完成业务的工作线程

在一个应用程序中, 如果用户线程全部结束了, 意味着程序需要完成的业务操作已经结束, 系统可以退出了。
所以当系统只剩下守护进程的时候, Java 虚拟机会自动退出。
反之, 如果程序中的用户进程还在执行中, Java 虚拟机会等待器执行完成才结束。

2 线程的创建

2.1 继承 Thread 类, 重写 run 方法

// 定义自己的线程逻辑
public class MyThread extend Thread {
    @Override
    public void run() {
        System.out.println("Thread is running " + Thread.currentThread().getName());
    }
}

// 启动线程
new MyThread().start()

2.2 实现 Runnable 接口

public void createThread() {
    // 定义自己的线程逻辑
    Thread thread = new Thread(new Runnable(){
        @Override
        public void run() {
            System.out.println("Thread is running " + Thread.currentThread().getName());
        }
    });

    thread.start();
}

2.3 实现 Callable 接口

@FunctionalInterface
public interface Callable<V> {
    V call() throws Exception;
}

Callable 接口和 Runnable 接口类似, call 方法里面就是需要线程执行的逻辑, 不同的是 Callable 有返回值
同时还有一个隐藏的不同点, Callable 内部可以抛出异常, 同时这个异常是可以被捕获的, 所以可以通过异常再做一次容错处理。

public void createThread() {
    // 实现 Callable 实现线程
    Callable<String> callable = new Callable<>() {
        @Override
        public String call() throws Exception {
            System.out.println("Thread is running " + Thread.currentThread().getName());
            Thread.sleep(3000L);
            return "finish";
        }
    };

    // 这里借助线程池来实现线程
    ExecutorService executorService = Executors.newSingleThreadExecutor();

    // 提交任务到线程池, Future 是对执行结果的封装
    Future<String> future = executorService.submit(callable);

    try {
        // 尝试获取执行结果
        // 注意: Future.get 方法是一个阻塞方法。如果对应的线程这时候还没有执行完成, 调用这个方法, 会阻塞当前线程
        String result = future.get();
        System.out.println("Thread's result:" + result);

    } catch(Exception e) {
        e.printStackTrace();
    } finally {
        // 关闭线程池
        executorService.shutdown();
    }    
}

2.4 创建 FutureTask 实例

FutureTask 的 UML 图:
Alt 'FutureTask 的 UML 图'

可以发现: FutureTask 还实现了 Runnable 接口, 所以可 FutureTask 也可以当做 Runnable 使用。
同时其还实现了一个 Future 的接口。

Futrue 接口的定义如下

public interface Future<V> {

    boolean cancel(boolean mayInterruptIfRunning);

    boolean isCancelled();

    boolean isDone();

    V get() throws InterruptedException, ExecutionException;

    V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
}

Futrue 主要是用于子线程将自己的执行结果返回给主线程:

  1. 主线程将任务提供给子线程时, 会立即返回一个 Future 的返回值
  2. 一开始这个对象的返回值会是空的, 后续子线程执行完成后, 会把返回值翻到这个对象内
  3. 主线程就可以主动通过这个对象获取到执行结果
  4. 因为主线程主动获取执行结果时, 可能子线程还未执行完成, 所以获取返回结果的方法是阻塞的, 主动获取返回结果时, 如果还未有返回值时, 主线程将会被阻塞等待到有返回结果

通过 Future 的声明, 可以知道 Future 除了获取返回结果外, 还具备了取消任务, 获取任务是否完成等功能。

public void createThread() {
    
    Callable<String> callable = new Callable<>() {
        @Override
        public String call() throws Exception {
            System.out.println("Thread is running " + Thread.currentThread().getName());
            Thread.sleep(3000L);
            return "finish";
        }
    };

    // 创建 FutureTask 实例
    FutureTask<String> futureTask = new FutureTask<String>(callable);

    // 创建线程池
    ExecutorService executorService = Executors.newSingleThreadExecutor();
    
    // FutrueTask 执行方式一
    // 此处可以不要返回值, 通过入参的 futureTask 获取执行结果
    Future<?> future = executorService.submit(futureTask);

    // FutureTask 执行方式二, 当 Runnable 使用
    // new Thread(futureTask).start();

    try {
        // 同样的会阻塞当前线程
        // 通过 FutureTask 创建的, 返回结果放在自身上, 不在 executorService.submit 的返回值
        // 通过 future 和 futureTask 都会阻塞当前线程
        String result = futureTask.get();
        System.out.println("Thread's result:" + result);
        
    } catch (Exception e) {
        e.printStackTrace();
    } finish {
        executorService.shutdown();
    }
}

4 种方式的对比

  1. Thread: 编写简单, 但是不能再继承其他父类, 其内部实际还是通过 Runnable 实现的
  2. Runnable: 多个线程可以共用一个 Runnable 对象, 适合多线程处理同一个任务的情况。没法知道线程的执行情况
  3. Callable: 具备和 Runnable 一样的优势的同时, 可以知道线程的执行情况。但是需要借助 ExecutorService, 没法指定工作线程执行
  4. FutureTask: 具备了 Runnable 和 Callable 的所有优点, 缺点就是编写复杂

3 线程的属性

3.1 tid

线程 Id, 用于标记不同的线程 (某个编号的线程结束后, 这个编号可能别后续创建的线程使用)。

3.2 name

线程名称, 面向人的一个属性, 用于区分不同的属性, 默认为 Thread-数字。 在实际开发中, 尽可能的自定义自己的线程名, 这样在后续的问题定位排查有帮助
(同时记得不要重复, Java 允许存在线程名相同的情况, 但是这会影响到后面问题的定义)。

3.3 priority

用于系统的线程调度用的, 表示希望某个线程能够优先得到运行。Java 定义了 1 - 10 个级别, 值越大, 优先级越高, 默认为 5。在实际使用中, 尽可能的不要自定义优先级, 可能会出现意想不到的问题, 比如线程饥饿。

3.4 daemon

是否为守护线程。一个线程是用户线程还是守护线程, 通过这个属性进行区分。
true: 表示这个线程为守护线程, 否则为用户线程, 这个属性的设置需要在线程启动之前进行设置才有效, 默认为 false, 用户线程。

3.5 threadStatus

线程状态, 标识当前线程的处于什么样的一样状态, 具体的取值后面分析。

其实 Thread 身上还有其他几个属性, 基本不是什么重要的属性, 就不展开了。

4 线程状态

4.1 状态取值

线程状态说明
NEW初始状态。线程已创建, 但是未启动, 既未调用 start 方法
RUNNABLE运行状态。她包括 2 个状态: 准备状态 和 运行状态
BLOCKED阻塞状态。线程阻塞于锁 或者 发起了阻塞式 I/O 操作 (Socket 读写)
WAITING等待状态。当前线程需要等待其他线程执行一下特定的操作(通知, 中断)
TIME_WAITING超时等待状态。和 WAITING 类似, 区别就是这个状态的等待是有时间限制的
TERMINATED终止状态。线程的需要执行的任务已完成。

4.2 线程状态的转换

如图:
Alt '线程状态转换'

4.2.1 New

通过 new Thread() 创建出 Thread 实例, 实例的默认的状态就是 New

4.2.2 Runnable

Java 中的 Runnalbe 状态实际可以再细分为 Ready 和 Running。线程处于 Runnable 不一定就是在执行中的, 也有可能是在 Ready 中,
具体什么时候从 Ready 变为 Running, 完全取决于系统的调度。

4.2.3 Waiting

等待中状态, 处于等待状态的线程, 正在等待其他线程去执行一个特定的操作。

从 Runnable 转到 Waiting 的方式有

  1. Object.join()
  2. Ojbect.wait()
  3. Lock.lock(), 尝试获取锁, 获取锁失败时
  4. LockSupport.park()

从 Waiting 转到 Runnable 的方式有

  1. Object.notify()
  2. Ojbect.notifyAll()
  3. LockSupport.uppark(Thread)

4.2.4 Time_Waiting

带超时时间的等待状态。

从 Runnable 转到 Timed_waiting 的方式有

  1. Thead.sleep(long)
  2. Object.wait(long)
  3. Thread.join(long)
  4. Lock.tryLock(long, TimeUnit)
  5. LockSupport.parkNanos()
  6. LockSupport.parkUntil()

从 Timed_Waiting 转到 Runnable 的方式有

  1. Object.notify()
  2. Ojbect.notifyAll()
  3. LockSupport.uppark(Thread)

4.2.5 Blocked

阻塞状态, 此时线程无任何的处理能力

从 Runnable 转到 Blocked 的方式有

  1. 获取 synchronized 锁失败

从 Blocked 转到 Runnable 的方式有

  1. 获取 synchronized 锁成功

备注(待考证):
WAITING 和 BLOCKING 之间也存在着转换, 当多个线程阻塞于同一个锁时, 他们都处于 WAITING 状态, 当有一个线程释放锁了, 上面的线程会同时争取锁, 争取到锁的线程会进入到 RUNNABLE, 没有争取到的会进入到 BLOCKED。

4.2.6 Terminated

终止状态。
线程中的业务业务代码执行完成, 结束逻辑。

5 线程的一些基本操作

5.1 sleep

sleep(long mills) 是 Thread 的一个静态方法。
可以让当前线程进入休眠, 休眠的时间由指定的参数决定。

调用这个方法会导致线程状态变为 Timed_waiting。

5.2 wait

wait() / wait(long mills) 是 Object 的一个方法, 可以让执行这个方法的线程暂停 (进入到 Waiting / Timed_waiting)。
wait() / wait(long mills) 在使用之前需要先获取到锁, 才能进入暂停。即只能在同步代码块中使用, 同时内部要调用代码块锁住的对象的 wait()


Object lock = new Object();
Object lock2 = new Object();

// 没有在代码块中, 抛异常
//lock.wait();

// 锁住了 lock 对象
synchronized (lock) {
    try {

        // 没有锁住 lock2, 调用 lock2.wait 方法会抛异常
        //lock2.wait();

        // 正常沉睡
        lock.wait();
    } catch(InterruptedException e) {
        e.printStackTrace();
    }
}

wait 和 sleep 的区别

  1. wait 是 Object 的一个方法, sleep 是 Thread 的一个静态方法
  2. wait 需要在获取到对应的锁的时候才能使用(也就是在同步代码块, 或者同步方法内), sleep 则不需要
  3. wait 方法在执行时, 会释放自身的拥有的锁, 而 sleep 如果拥有锁, 则不会释放
  4. wait(long mills) / sleep 方法会在指定的休眠时间达到后, 重新运行。但是 wait() 方法需要其他线程调用对应的锁对象的 notify() 或者 notifyAll() (这 2 个方法也都是需要先获取到对应的锁), 进行通知后, 才有可能继续执行 (有可能同时多个线程在等待, 但是锁只有一个, 只能在等待的线程中选择一个进行唤醒)

5.3 join

join() / join(long mills) 是 Thread 的一个方法。主要用于让当前线程等待指定的线程执行完成。


Thread waitThread = new Thread(() -> {
    try {
        Thread.sleep(30000L);
        System.out.println("son thread finish");
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
});
waitThread.start();


try {
    // 当前线程(主线程)进入暂停状态, 等待 t 线程执行完。
    waitThread.join();
} catch (InterruptedException e) {
    e.printStackTrace();
}
System.out.println("main thread finish");

5.4 yeild

yeild() 是 Thread 的一个静态方法。
作用: 使当前线程主动放弃其对处理器的占用, 这可能导致当前线程被暂停。这个方法是不可靠的, 有可能调用之后, 前线程还在继续执行。

5.5 interrupt

interrupt() 是 Thread 的一个方法, 调用这个方法, 可以向指定的线程发送一个信号, 让其终止, 但是最终是否能够终止, 由线程内部决定。

原理:

  1. 线程内部维护了一个 isInterrupted 的变量 (这个变量不在 Java 代码里面维护, 而是在 JVM 的代码里面), 取值范围为 0 (false), 1 (true)
  2. 调用线程的 interrupt() 方法, 会把这个标志符设为 1
  3. 当线程的状态从 Runnable 变为其他的状态时, 检测到这个标识为 1, 就会抛出 InterruptedException 异常, 同时把标志重新恢复为 0
  4. 线程的 wait/sleep/join 等方法, 都可以改变线程的状态

复位 (中断标识从 true 恢复回 false):

  1. 可以直接调用 Thread 的 静态方法 interrupted() 可以将中断标志恢复为 false.
  2. 线程抛出 InterruptedException 异常。

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

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

相关文章

使用Terraform创建Docker镜像和容器

为了实现自动化操作&#xff0c;Terraform需要明确指定所使用的提供者。因此&#xff0c;在主要的main.tf文件中&#xff0c;需要提供提供者的名称、源和版本信息。对于Docker&#xff0c;可以在main.tf中使用以下代码块。 1 Terraform配置模块 使用块和资源创建Terraform脚本…

nodejs+vue+elementui+express青少年编程课程在线考试系统

针对传统线下考试存在的老师阅卷工作量较大&#xff0c;统计成绩数据时间长等问题&#xff0c;实现一套高效、灵活、功能强大的管理系统是非常必要的。该系统可以迅速完成随机组卷&#xff0c;及时阅卷、统计考试成绩排名的效果。该考试系统要求&#xff1a;该系统将采用B/S结构…

C++类与对象(6)—初始化列表、explicit关键字、static成员

目录 一、初始化列表 1、定义 2、注意事项 3、尽量使用初始化列表初始化 4、初始化顺序 二、 explicit关键字 1、定义 2、特点 三、static成员 1、定义 2、特性 3、例题 一、初始化列表 下面这段代码可以正常编译&#xff1a; class A { private:int _a1;//成员…

innovus如何在floorplan view显示所有module

我正在「拾陆楼」和朋友们讨论有趣的话题&#xff0c;你⼀起来吧&#xff1f; 拾陆楼知识星球入口 如题&#xff0c;innovus的图形界面在floorplan view下默认只能显示instance数量超过100个的module&#xff0c;如果要显示更小的module&#xff0c;需要在VIEW-Set Perference…

LeetCode Hot100 394.字符串解码

题目&#xff1a; 给定一个经过编码的字符串&#xff0c;返回它解码后的字符串。 编码规则为: k[encoded_string]&#xff0c;表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。 你可以认为输入字符串总是有效的&#xff1b;输入字符串中没有额外的…

东胜物流软件 SQL注入漏洞复现

0x01 产品简介 东胜物流软件是一款致力于为客户提供IT支撑的 SOP&#xff0c; 帮助客户大幅提高工作效率&#xff0c;降低各个环节潜在风险的物流软件。 0x02 漏洞概述 东胜物流软件 TCodeVoynoAdapter.aspx、/TruckMng/MsWlDriver/GetDataList、/MvcShipping/MsBaseInfo/Sav…

C语言——I /深入理解指针(二)

一、数组名的理解 int arr[10] {1,2,3,4,5,6,7,8,9,10}; int *p &arr[0];这⾥我们使⽤ &arr[0] 的⽅式拿到了数组第⼀个元素的地址&#xff0c;但是其实数组名本来就是地址&#xff0c;⽽且 是数组⾸元素的地址&#xff0c;我们来做个测试。 #include <stdio.…

C++ :静态成员

静态成员 静态成员就是在成员变量和成员函数前加上关键字 static &#xff0c;称为静态成员 静态成员分为&#xff1a; 静态成员变量 1.所有对象共享同一份数据 2.在编译阶段分配内存 3.类内声明&#xff0c;类外初始化 静态成员函数 1.所有对象共享同一个函数 2.静态成…

计算机毕业设计springboot+vue高校田径运动会报名管理系统61s38

高校田径运动会管理采用java技术&#xff0c;基于springboot框架&#xff0c;mysql数据库进行开发&#xff0c;实现了首页、个人中心、运动员管理、裁判员管理、场地信息管理、项目类型管理、比赛项目管理、比赛报名管理、比赛成绩管理、通知公告管理、留言板管理、交流论坛、系…

1.ORB-SLAM3中如何保存多地图、关键帧、地图点到二进制文件中

1 保存多地图 1.1 为什么保存(视觉)地图 因为我们要去做导航&#xff0c;导航需要先验地图。因此需要保存地图供导航使用&#xff0c;下面来为大家讲解如何保存多地图。 1.2 保存多地图的主函数SaveAtlas /*** brief 保存地图* param type 保存类型*/ void System::SaveAtlas(…

Kubernetes基础入门:Kubernetes的有关概述

Kubernetes基础入门&#xff1a;Kubernetes的有关概述 一、摘要二、为什么需要 Kubernetes&#xff1f;三、Kubernetes 的功能架构 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 一、摘要 随着云计算和容器技术的快速发展&#xff0c;现代…

十分钟搭建VScode C/C++运行环境

一、下载配置vscode 1.下载安装VScode 地址&#xff1a;https://code.visualstudio.com/download 下载后&#xff0c;运行安装程序 (VSCodeUserSetup-{version}.exe)。这只需要一分钟。安装程序会将 Visual Studio Code 添加到环境变量中%&#xff0c;可以使用CMD键入“code”…

SQL FULL OUTER JOIN 关键字:左右表中所有记录的全连接解析

SQL RIGHT JOIN关键字 SQL RIGHT JOIN关键字返回右表&#xff08;table2&#xff09;中的所有记录以及左表&#xff08;table1&#xff09;中的匹配记录。如果没有匹配&#xff0c;则左侧的结果为0条记录。 RIGHT JOIN语法 SELECT column_name(s) FROM table1 RIGHT JOIN ta…

Javaweb之Vue组件库Element之Dialog对话框的详细解析

4.3.3 Dialog对话框 4.3.3.1 组件演示 Dialog: 在保留当前页面状态的情况下&#xff0c;告知用户并承载相关操作。其企业开发应用场景示例如下图所示 首先我们需要在ElementUI官方找到Dialog组件&#xff0c;如下图所示&#xff1a; 然后复制如下代码到我们的组件文件的templ…

【Qt】之QSet使用

描述 QSet类是一个模板类&#xff0c;它提供基于散列表的集合。 QSet是Qt的通用容器类之一。它以未指定的顺序存储值&#xff0c;并提供非常快速的值查找。在内部,QSet是作为QHash实现的。 下面是一个带有QString值的QSet示例: QSet<QString> set;插入方式1&#xff…

第1章 爬虫基础

目录 1. HTTP 基本原理1.1 URI 和 URL1.2 HTTP 和 HTTPS1.3 请求1.3.1 请求方法1.3.2 请求的网址1.3.3 请求头1.3.4 请求体 1.4 响应1.4.1 响应状态码1.4.2 响应头1.4.3 响应体 2. Web 网页基础2.1 网页的组成2.1.1 HTML2.1.2 CSS2.1.3 JavaScript 2.2 网页的结构2.3 节点树及节…

【教学类-06-10】20231126 X-Y数字分合-分-下空左

结果展示&#xff1a; 背景需求&#xff1a; 数字分合&#xff0c;这一次空在左侧 代码展示&#xff1a; X-Y 之间的分合题-分-空在右侧 时间&#xff1a;2023年11月26日 21:46 作者&#xff1a;阿夏 import random from win32com.client import constants,gencache from win3…

达索系统3DEXPERIENCE WORKS 2024电磁仿真功能

在设计工作中&#xff0c;将复杂的模型进行网格分割是必不可少的一步&#xff0c;这样可以化繁而简&#xff0c;也可以让后续的工作更容易开展。 电磁仿真可帮助您在复杂、嘈杂的电磁环境中提高效率&#xff0c;在确保兼容性的同时&#xff0c;保障出众性能。 一系列专用求解器…

Redux在React中的使用

Redux在React中的使用 1.构建方式 采用reduxjs/toolkitreact-redux的方式 安装方式 npm install reduxjs/toolkit react-redux2.使用 ①创建目录 创建store文件夹&#xff0c;然后创建index和对应的模块&#xff0c;如上图所示 ②编写counterStore.js 文章以counterStore…

【Linux】 file命令使用

file命令 file命令用于辨识文件类型。 语法 file [参数] [文件名] who命令 -Linux手册页 命令选项及作用 执行令 file --help 执行命令结果 参数 -b  列出辨识结果时&#xff0c;不显示文件名称&#xff1b;-i&#xff1a;显示MIME类型&#xff1b;-z&#xff1a;对…