Java Thread 介绍

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

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 异常。

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

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

相关文章

kali linux英文改中文

如果英语基础较好的同学可以不用调整 反之则需要 找到终端&#xff08;就是输入命令的那个地方 如下&#xff09;点击它出现命令终端 切换为root用户&#xff0c;命令为&#xff1a; sudo dpkg-reconfigure locales 然后回车 找到这个zh_CN 然后回车 鼠标下键选中并且回车 输…

耶鲁博弈论笔记

编辑记录&#xff1a; 1126&#xff1a;开个新坑&#xff0c;耶鲁大学的博弈论课程&#xff0c; 和专业相关不大&#xff0c;纯兴趣&#xff0c;尽量写好一点吧 1. 首先指出博弈论是一种研究策略形式的方法&#xff0c;对于经济学中&#xff0c;完全竞争市场只能被动接受均衡…

IT问题解答类型网站源码

问答网是一款为IT工程师提供的问答平台&#xff0c;旨在帮助用户在线获取专业知识和相关问题的答案。在问答网&#xff0c;用户可以轻松找到其他人的问答问题&#xff0c;并在这里寻求解答。如果您有任何想要解决的问题&#xff0c;都可以在此发布问题并得到其他同行的解答。 …

【STL】string类 (下)

目录 1&#xff0c;insert 2&#xff0c;erase 3&#xff0c;find 4&#xff0c;replace 5&#xff0c;rfind 6&#xff0c;substr 7&#xff0c;find_first_of 8&#xff0c;find_first_not_of 9&#xff0c;find_last_of 10&#xff0c;operator 11&#xff0c;ge…

Qt TCP网络上位机的设计(通过网络编程与下位机结合)

目录 TCP 协议基础 QTcpServer 和 QAbstractSocket 主要接口函数 TCP 应用程序 1.服务端 2.客户端 上位机通过网络编程与下位机实现通信 TCP 协议基础 传输控制协议&#xff08;TCP&#xff0c;Transmission Control Protocol&#xff09;是一种面向连接的、可靠的、基于…

Camtasia Studio2024专业的屏幕录制和视频剪辑软件

Camtasia2024专业的屏幕录制和视频剪辑软件3000多万专业人士在全球范围内使用Camtasia展示产品&#xff0c;教授课程&#xff0c;培训他人&#xff0c;以更快的速度和更吸引人的方式进行沟通和屏幕分享。使您在Windows和Mac上进行录屏和剪辑创作专业外观的视频变得更为简单。 …

BGP选路实验

要求 1 使用PreVal策略&#xff0c;确保R4通过R2到达192.168.10.0/24 2 使用AS_Path策略&#xff0c;确保R4通过R3到达192.168.11.0/24 3 配置MED策略&#xff0c;确保R4通过R3到达192.168.12.0/24 4 使用Local Preference策略&#xff0c;确保R1通过R2到达192.168.1.0/24 5 使…

【古诗生成AI实战】之五——加载模型进行古诗生成

回顾上一篇博客&#xff0c;我们已经成功地训练了我们的模型&#xff0c;并将其保存下来。这是一个重要的里程碑&#xff0c;因为训练好的模型是我们进行文本生成的基础。 现在&#xff0c;接下来的步骤是加载这个训练好的模型&#xff0c;然后使用它来生成古诗。 本章的内容属…

打印菱形-第11届蓝桥杯选拔赛Python真题精选

[导读]&#xff1a;超平老师的Scratch蓝桥杯真题解读系列在推出之后&#xff0c;受到了广大老师和家长的好评&#xff0c;非常感谢各位的认可和厚爱。作为回馈&#xff0c;超平老师计划推出《Python蓝桥杯真题解析100讲》&#xff0c;这是解读系列的第9讲。 打印菱形&#xff…

Android 虚拟机与类加载机制

1、Dalvik 虚拟机 Android 应用程序运行在 Dalvik/Art 虚拟机上&#xff0c;并且每一个应用程序都有一个单独的 Dalvik/Art 虚拟机实例。 1.1 JVM 与 Dalvik Dalvik 虚拟机也算是一个 Java 虚拟机&#xff0c;它是按照 JVM 虚拟机规范实现的&#xff0c;二者的特性差不多&am…

爬楼梯(力扣LeetCode)动态规划

爬楼梯 题目描述 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢&#xff1f; 示例 1&#xff1a; 输入&#xff1a;n 2 输出&#xff1a;2 解释&#xff1a;有两种方法可以爬到楼顶。 1 阶 1 阶2 阶 示…

树状数组专题

折叠 区间修改&#xff0c;区间查询&#xff0c;这一类题通常都可以使用线段树解决&#xff0c;但对于此题&#xff0c;树状数组同样可以&#xff0c;而且常数较小&#xff0c;代码简单。 思路&#xff1a; 考虑使用树状数组去维护差分数组&#xff0c;即对于 a i a_i ai​,我们…

找不到vcomp120.dll该如何修复?vcomp120.dll丢失的5个可行解决方法

本文将对vcomp120.dll文件的丢失原因进行详细分析&#xff0c;并提供五个有效的修复方法。同时&#xff0c;本文还将深入介绍vcomp120.dll文件的作用及其在程序运行中的重要性。 一、vcomp120.dll文件丢失原因 操作系统损坏&#xff1a;由于病毒感染、系统错误等原因&#xf…

linux复习笔记04(小滴课堂)

软件安装rpm方式介绍&#xff1a; 先去挂载光盘&#xff1a; 要确保这是已连接状态。 我们查看到已经挂载成功了。 进到这个目录下。 我们可以看到这有很多rpm软件包。 man rpm: 可以看到很多参数&#xff0c;但是我们不需要全部掌握。 举例&#xff1a; 这就是告诉我们需要安…

docker (简介、dcoker详细安装步骤)- day01

一、 为什么出现 Docker是基于Go语言实现的云开源项目。 Docker的主要目标是“Build&#xff0c;Ship and Run Any App,Anywhere”&#xff0c;也就是通过对应用组件的封装、分发、部署、运行等生命周期的管理&#xff0c;使用户的APP&#xff08;可以是一个WEB应用或数据库应…

WiFi的CSMA/CA竞争窗口流程简述

1、若站点最初有数据要发送&#xff08;不是发送不成功再进行重传的那种&#xff09;&#xff0c;且检测到信道空闲&#xff0c;在等待DIFS后&#xff0c;就发送整个数据帧。 2、否则&#xff0c;站点执行退避算法。一旦检测到信道忙&#xff0c;就冻结退避计时器。只要信道空…

深度学习之循环神经网络

视频链接&#xff1a;6 循环神经网络_哔哩哔哩_bilibili 给神经网络增加记忆能力 对全连接层而言&#xff0c;输入输出的维数固定&#xff0c;因此无法处理序列信息 对卷积层而言&#xff0c;因为卷积核的参数是共享的&#xff0c;所以卷积操作与序列的长度无关。但是因为卷积…

北塞浦路斯土耳其共和国关于成立欧洲数字股票交易所企业交流会

在地中海的温暖波涛中&#xff0c;北塞浦路斯土耳其共和国这个古老而充满活力的国家正成为全球关注的焦点。2023年11月22日至11月24日&#xff0c;为期三天的北塞浦路斯土耳其共和国关于成立欧洲数字股票交易所企业交流会隆重谢幕&#xff0c;北塞副总统&#xff0c;经济部长&a…

【学习记录】从0开始的Linux学习之旅——驱动模块编译与加载

一、概述 Linux操作系统通常是基于Linux内核&#xff0c;并结合GNU项目中的工具和应用程序而成。Linux操作系统支持多用户、多任务和多线程&#xff0c;具有强大的网络功能和良好的兼容性。本文主要讲述如何编译及加载linux驱动模块。 二、概念及原理 应用程序通过系统调用与内…

STK Components 二次开发-创建地面站

1.地面站只需要知道地面站的经纬高。 // Define the location of the facility using cartographic coordinates.var location new Cartographic(Trig.DegreesToRadians(-75.596766667), Trig.DegreesToRadians(40.0388333333), 0.0); 2.创建地面站 创建方式和卫星一样生成对…