并发编程系列之线程的启动终止

2023-10-27

前言

上节我们对线程有了个基本的概念和认识,从线程状态转变过程我们也已经知道了线程通过调用start方法进行启动,直到run方法执行线程结束,今天我们就来详细的说说启动和终止线程的细节,OK,让我们开始今天的并发之旅吧。

 

创建线程

在使用一个线程之前我们需要先构造线程,即new一个线程

Thread thread = new Thread();

线程对象在构建的时候需要提供线程所需要的属性,如线程组、优先级等等,下面我们看下如下的源代码:

private void init(ThreadGroup g, Runnable target, String name,
                     long stackSize) {
       // 如果新线程名字为null,抛出异常
       if (name == null) {
           throw new NullPointerException("name cannot be null");
       }
       // 获取该线程的父线程
       Thread parent = currentThread();
       // 获取安全管理组件
       SecurityManager security = System.getSecurityManager();
       if (g == null) {
           // 如果安全组件不为空,就调用SecurityManager的线程组
           if (security != null) {
               g = security.getThreadGroup();
           }
           // 如果SecurityManager为空,并且该线程的线程组也为空,则调用其父线程的线程组
           if (g == null) {
               g = parent.getThreadGroup();
           }
       }
       // 显示通过 允许线程访问线程组
       g.checkAccess();

       // 检查访问权限
       if (security != null) {
           if (isCCLOverridden(getClass())) {
               security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
           }
       }
       // 对线程组中未使用的线程计数器+1
       g.addUnstarted();
       // 调用父线程的线程组
       this.group = g;
       // 调用父线程守护线程
       this.daemon = parent.isDaemon();
       // 调用父线程的优先级
       this.priority = parent.getPriority();
       // 将字符串转换为新的字符数组
       this.name = name.toCharArray();
       // 加载父线程的ContextClassLoader
       if (security == null || isCCLOverridden(parent.getClass()))
           this.contextClassLoader = parent.getContextClassLoader();
       else
           this.contextClassLoader = parent.contextClassLoader;
       this.inheritedAccessControlContext = AccessController.getContext();
       this.target = target;
       setPriority(priority);
       // 加载父线程的ThreadLoad
       if (parent.inheritableThreadLocals != null)
           this.inheritableThreadLocals =
               ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
       // 指定堆栈大小
       this.stackSize = stackSize;
       // 设置线程ID
       tid = nextThreadID();
   }

从上面源码我们可以看到,一个新的线程是由其父线程来进行空间分配的,子线程继承父线程的优先级,是否为守护线程、contextClassLoader以及ThreadLocal,最后分配一个唯一的线程ID,新的线程就被创建完毕,在堆内存中等待着被运行。

也可以通过构建Runnable对象来构建线程:

Thread t1 = new Thread(new Runnable() {
     @Override
     public void run() {
         ...
     }
   }, "t1");

启动线程

线程创建完毕,我们就可以开始使用该线程了,启动一个线程很简单,直接使用start方法

thread.start();

我们再来对start方法的源码进行分析:

/**
* 该方法不是给main线程和系统线程调用的
* 由虚拟机创建和设置的组线程
*/
public synchronized void start() {
       // 0代表线程的状态NEW,初始化状态,如果线程不是初始化状态,则抛出异常
       if (threadStatus != 0)
           throw new IllegalThreadStateException();
       
       // 通知线程组,该线程即将被启动,添加到组的运行线程列表中,同时未使用线程数计数器-1
       group.add(this);

       // 启动成功标识符
       boolean started = false;
       try {
           start0();
           started = true;
       } finally {
           try {
               if (!started) {
                   // 如果启动不成功,线程组做相应启动失败处理
                   group.threadStartFailed(this);
               }
           } catch (Throwable ignore) {
               // 如果什么都不做,则
           }
       }
   }

执行start方法之后,当前线程(父线程)就会同步通知虚拟机,只要线程规划器空闲,就应该立即启动该线程(调用start方法的线程)。

线程中断

线程中断表示一个运行中的线程是否被其他线程进行中断操作,中断在线程中是由一个Boolean值来标识的,我们看下如何判断当前线程中断状态和相关操作:

  • 判断线程是否被中断:isInterrupted方法

public static void main(String[] args) throws Exception{
       Thread thread1 = new Thread();
       thread1.start();
       System.out.println("线程:"+thread1.getName()+"是否被中断:"+thread1.isInterrupted());
   }
结果:线程:Thread-0 是否被中断:false
  • 中断线程方法:interrupt方法

thread1.interrupt();
System.out.println("线程:"+thread1.getName()+"是否被中断:"+thread1.isInterrupted());

结果:线程:Thread-0是否被中断:true
  • 清除中断标识:currentThread().interrupt()方法

public static void main(String[] args) throws Exception{
       Thread thread1 = new Thread();
       thread1.start();
       thread1.interrupt();
       System.out.println("线程:"+thread1.getName()+"是否被中断:"+thread1.isInterrupted());
       thread1.currentThread().interrupt();
       System.out.println("线程:"+thread1.getName()+"是否被中断:"+thread1.isInterrupted());
   }
结果:
线程:Thread-0是否被中断:true
线程:Thread-0是否被中断:false

 

安全的终止线程

上面提到的中断方式是一种比较常见的终止方式,除此之外还有2种方式,一个是使用一个标志位来通知线程终止,还有一个就是使用stop方法(不推荐,下面会详解),下面我们先看看使用标志位和中断如何终止线程:

  • 使用标志位Boolean值

public class ThreadStartDemo {
   // 线程终止标识位
   static volatile Boolean flag = false;
   static int time1 = 0;
   static int time2 = 0;

   public static void main(String[] args) throws Exception {
       Thread t1 = new Thread(new Runnable() {
           @Override
           public void run() {
               System.out.println("线程1启动");
               while (!flag) {
               }
               time2 = Integer.parseInt(DateUtil.getNowTimestamp());
               int i = time2 - time1;
               System.out.println("线程t1退出,等待时间为" + i + "秒");
           }
       }, "t1");

       Thread t2 = new Thread(new Runnable() {
           @Override
           public void run() {
               time1 = Integer.parseInt(DateUtil.getNowTimestamp());
               System.out.println("线程2启动,5秒后修改flag的值");
           }
       }, "t2");

       t1.start();
       t2.start();
       t2.sleep(5000);
       flag = true;
   }
}

运行结果如下:

5秒过后:注意看,线程以及终止运行

  • 使用中断机制终止线程

public class ThreadExitDemo extends Thread {
   public void run() {
       System.out.println("线程运行中!!!");
       System.out.println("请输入任意键盘值来发出中断信号");
   }

   public static void main(String[] args) throws Exception {
       Thread thread = new ThreadExitDemo();
       thread.start();
       System.in.read();
       thread.interrupt();
       thread.join();
       System.out.println("线程已经退出!!!");
   }
}

运行结果如下:

线程中几个废弃的方法

在线程运行过程中还有三个被抛弃的方法,分别是suspend()、resume()、stop()方法,分别代表暂停、恢复和停止的意思,那么为什么这3个方法被废弃了呢?

  • suspend方法在被调用后,线程不会释放已经占有的资源(如锁),而是占着资源进入睡眠状态,这样容易引起死锁问题;

  • resume和suspend是成对出现的,既然suspend被抛弃了,当然好基友resume也就没有用武之地了,也是不被推荐使用的方法;

  • stop方法在终止一个线程时,不会保证线程的资源正常释放,通常是没有给予线程完成资源释放工作的机会,这就会导致程序可能工作在不确定的状态下,导致一个线程任务不能完整的执行完就被直接终止,程序执行的正确性得不到保证;

 

以上就是今天所讲的线程启动和终止的相关内容,希望通过这篇文章,你能对如何正确的启动和关闭一个线程有所掌握,感谢您的阅读!!!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

并发编程系列之线程的启动终止 的相关文章

  • Java并发编程实战——并发容器之ThreadLocal及其内存泄漏问题

    文章目录 ThreadLocal的简介 ThreadLocal的实现原理 ThreadLocalMap详解 ThreadLocal内存泄漏问题 ThreadLocal的使用场景 ThreadLocal的简介 之前写过用ThreadLocal
  • java并发总结

    一 并发基础 1 进程与线程 进程 程序由指令和数据组成 但这些指令要运行 数据要读写 就必须将指令加载至 CPU 数据加载至内存 在指令运行过程中还需要用到磁盘 网络等设备 进程就是用来加载指令 管理内存 管理 IO 的 当一个程序被运行
  • 并发编程系列之Exchanger

    前言 上面我们介绍了信号量 再来说说交换者 这个东西用的不是很多 所以一般也不被经常关注 但是我们还是最好了解下 下面我将从什么是Exchanger以及如何使用Exchanger两个方面谈谈这个用于线程间协调的工具类 什么是Exchange
  • Java中的Semaphore信号量机制

    目录 什么是信号量机制 Semaphore工作流程 Semaphore使用方式 什么是信号量机制 信号量机制是一种通过使用计数器来控制共享资源访问的机制 计数器计数的是共享资源的访问许可 如果计数器大于0则允许访问 如果为0 则拒绝访问 J
  • 一次线上的GC问题排查

    6 19号下午 线上系统出现了一次实时链路数据 不通畅的问题 业务方反应更新的增量数据没有流入到HA3搜索集群 登录机器后检查日志后发现 在周六晚上到周天下午 cr search merge 机器人schema统一 表增量数据猛增 初步估计
  • 【2021最新版】Java多线程&并发面试题总结(108道题含答案解析)

    文章目录 JAVA并发知识库 1 Java中实现多线程有几种方法 2 继承Thread类 3 实现Runnable接口 4 ExecutorService Callable Future有返回值线程 5 基于线程池的方式 6 4 种线程池
  • Callable和Future原理解析

    首先进行分析前 我们需要了解到的概念 Callable是一个接口 是用于创建线程执行里面有一个call方法 用于线程执行的内容 由业务自己定义 Future也是一个接口 可以异步的通过get方法获取到call返回的内容 比较常见的使用场景
  • java晋级赛 深入并发编程

    根据黑马java并发编程学习做的笔记 传送门 https www bilibili com video BV16J411h7Rd p 15 java晋级赛 深入并发编程 一 多线程基础 进程与线程 创建线程的方式及运行原理 创建线程的方式
  • java中的异步处理和Feature接口(一)

    文章目录 背景介绍 Feature接口 Feature接口和Tread的区别 Feature接口示例 Feature接口的局限性 背景介绍 想象这样一个场景 你可能希望为你的法国客户提供指定主题的热点报道 为实现这一功能 你需要向 谷歌或者
  • Callable接口详解

    Callable接口详解 Callable 返回结果并且可能抛出异常的任务 优点 可以获得任务执行返回值 通过与Future的结合 可以实现利用Future来跟踪异步计算的结果 Runnable和Callable的区别 1 Callable
  • 并发策略之分工原则

    本文主要思想来自 Java虚拟机并发编程 薛笛 译 为什么要用并发 并发是再在有限的资源下提高性能的有效手段 当然现在互联网环境下并发访问的现象也比比皆是 但是本文并不涉及处理并发访问 而是使用并发手段解决复杂任务的策略 另外关于并发和并行
  • BlockingQueue、ArrayBlockingQueue、LinkedBlockingQueue原理分析

    阻塞队列与非阻塞队 阻塞队列与普通队列的区别在于 当队列是空的时 从队列中获取元素的操作将会被阻塞 或者当队列是满时 往队列里添加元素的操作会被阻塞 试图从空的阻塞队列中获取元素的线程将会被阻塞 直到其他的线程往空的队列插入新的元素 同样
  • QT多线程基础

    文章目录 简介 相关名词 QT 运行方式 基础使用方法 void QObject moveToThread QThread targetThread 退出线程过程 wait 等待子线程的结束 实例 QT锁QMutex QMutexLocke
  • Java 中的Lock锁对象(ReentrantLock/ReentrantReadWriteLock)详解

    目录 1 Lock Objects 详解 2 Java 中的 Lock Objects 的实现原理 3 ReentrantLock 详解 4 ReentrantReadWriteLock 详解 5 Lock锁的等待和唤醒 6 Lock 和
  • CUDA编程问题记录:能否用CPU多线程调用CUDA核函数

    问题 能否在主机端创建CPU多线程 在每个线程里调用设备端核函数的caller函数 进而实现进一步的并行运行 例如有5张图片 对于每张图片都有N个GPU线程对其进行像素操作 但是此时是逐一对这5张图片处理的 想在主机端创建5个CPU线程 每
  • 说说JUC三个类:CountDownLatch,CyclicBarrier和Semaphore

    目录 CountDownLatch CyclicBarrier Semaphore 总结 在JUC中 有三个工具类来辅助我们进行并发编程 分别是 CountDownLatch CyclicBarrier和Semaphore CountDow
  • 线程的状态与切换

    Java中的线程的生命周期大体可分为5种状态 1 新建 初始化 NEW 新创建了一个线程对象 2 可运行 RUNNABLE 线程对象创建后 其他线程 比如main线程 调用了该对象的start 方法 该状态的线程位于可运行线程池中 等待被线
  • 并发编程系列之CountDownLatch对战Cyclicbarrier

    前言 前面我们介绍了并发容器和队列 今天我们来介绍几个非常有用的并发工具类 今天主要讲CountDownLatch和Cyclicbarrier这两个工具类 通过讲解并对比两个类的区别 OK 让我们开始今天的并发之旅吧 什么是CountDow
  • 死锁产生条件和解决办法

    死锁 死锁产生的四个条件 产生死锁必须同时满足以下四个条件 只要其中任一条件不成立 死锁就不会发生 互斥条件 线程要求对所分配的资源 如打印机 进行排他性控制 即在一段时间内某资源仅为一个线程所占有 此时若有其他线程请求该资源 则请求线程只
  • 多线程实现事务回滚

    多线程实现事务回滚 特别说明CountDownLatch CountDownLatch的用法 CountDownLatch num 简单说明 主线程 mainThreadLatch await 和mainThreadLatch countD

随机推荐

  • 计算机设备问题代码43,双击unknown device由于该设备有问题Windows已将其停止(代码 43)怎么办解决教程...

    金士顿U盘做的启动盘 8G 在别人的电脑上储存文件正常 但在自己的电脑上无法识别 在设备管理器中显示黄色叹号 属性显示 该设备存在问 题 windows已将其停止 代码43 本机win7系统 别人电脑为XP系统 已将 禁用 注册表 dos设
  • Linux 阻塞IO(等待队列)原理及架构

    一 阻塞操作 阻塞操作是指在执行折本操作时 若不能获得自愿 则挂起进程直到满足可操作性的条件后在进行操作 被挂起的进程进入休眠状态 被从调度器的运行队列移走 直到等待的条件被满足 假设recvfrom函数是一个系统调用 阻塞不是低效率 如果
  • vue -- 验证码

  • 25-python函数(低阶)

    一 函数的作用 函数的本质就是将一段具有独立功能的代码块整合到一个整体并命名 在需要的时候通过调用函数名完成某种需求 以提高代码的利用率 从而在稳定系统的同时减轻程序员的工作 二 函数的使用过程 函数使用分为两个步骤 先定义 后调用 定义函
  • Java设计模式之状态模式

    本文继续介绍23种设计模式系列之策略模式 何时使用 State模式在实际使用中比较多 适合 状态 的切换 因为我们经常会使用If else if else 进行状态切换 如果针对状态的这样判断切换反复出现 我们就要联想到是否可以采取Stat
  • Android相机-架构3

    目录 引言 1 Android相机的整体架构 2 相机 HAL 2 1 AIDL相机HAL 2 2 相机 HAL3 功能 3 HAL子系统 3 1 请求 3 2 HAL和相机子系统 3 2 1 相机的管道 3 2 2 使用 Android
  • TCP的拥塞控制(详解)

    在某段时间 若对网络中某一资源的需求超过了该资源所能提供的可用部分 网络性能就要变坏 这种情况就叫做网络拥塞 在计算机网络中数位链路容量 即带宽 交换结点中的缓存和处理机等 都是网络的资源 若出现拥塞而不进行控制 整个网络的吞吐量将随输入负
  • 【6 GoldenEye渗透笔记】

    1 前言 本文仅用于技术讨论与研究 不做任何导向 对于所有笔记中复现的这些终端 服务器或者实验环境 均为自行搭建的公开靶场 请勿在现实环境中模仿 操作 本文涉及到的工具仅就用到的方面做简要描述 如果想了解更详细的信息 请自行参阅其他技术资料
  • Java的多态性

    Java的多态性多态性严格来讲有两种描述形式 一 方法的多态性 1 方法的重载 同一个方法名称 会根据传入参数的类型及个数不同执行不同的方法体 2 方法的覆写 同一个方法名称 会根据子类的不同 实现不同的功能 二 对象的多态性 指的是发生在
  • 51Nod 2094 前缀和

    题目链接 https www 51nod com Challenge Problem html problemId 2094 include
  • unity3D简答题2

    游戏对象运动的本质 游戏对象运动的本质是对象Transform属性的变化 position决定位置 rotation决定旋转角度 请用三种方法以上方法 实现物体的抛物线运动 如 修改Transform属性 使用向量Vector3的方法 第一
  • canvas drawbitmap不出现_用Flutter做桌上弹球?聊聊绘图(Canvas&CustomPaint)API

    本文是Flutter中Canvas和CustomPaint API的使用实例 首先看一下列出最终目标 在程序运行后 显示一个小球 每次程序启动后 小球的样式均发生随机性变化 体现在大小 颜色和位置三点 小球运行的规律参考桌球或三维弹球游戏
  • Convolutional Pose Machine - 卷积姿态网络

    Convolutional Pose Machine 卷积姿态网络 论文主要解决 单人单目 RGB图像姿态估计问题 本篇论文也是现在非常火热的openpose开源项目的核心之一 这篇来自CMU的论文为单人姿态估计方面研究注入新的思路 论文网
  • Flutter初体验

    背景 今天在 lt 极客 gt APP上看到一个 使用Flutter快速构建集美观与高性能于一体的移动应用 的介绍 瞬间想试试 于是找到了Flutter官网 https flutter cn 并按步骤实操起来 手册 安装和环境配置 Flut
  • Docker部署单机Zookeeper

    配置Zookeeper安装目录 在宿主机配置zookeeper安装目录 docker develop zookeeper 并且在文件夹创建 data 和logs 目录 mkdir p docker develop zookeeper dat
  • 运行paddle-gpu相关项目报错

    报错1 RuntimeError PreconditionNotMet Cannot load cudnn shared library Cannot invoke method cudnnGetVersion Hint cudnn dso
  • JS 对象 Key使用变量代替

    JS 对象 Key使用变量代替 let key aa let data key value 普通 的对象类型数据中 直接用变量名填写key 最后 key会变成变成变量名的字符串 let data key value 使用中括号 以后 就会将
  • 实现点击img图片标签触发上传文件input按钮

    点击图片上传文件 简单有效 在网上找的一些文章都是A抄B B抄C 还都没啥用 自己蠢了 其实很简单 记录一下 Html 代码 div style width 30 margin left 3 height 1 img src images
  • Kendo UI开发教程(7): Kendo UI 模板概述

    Kendo UI 框架提供了一个易用 高性能的JavaScript模板引擎 通过模板可以创建一个HTML片段然后可以和JavaScript数据合并成最终的HTML元素 Kendo 模板侧重于UI显示 支持关键的模板功能 着重于性能而不是语法
  • 并发编程系列之线程的启动终止

    前言 上节我们对线程有了个基本的概念和认识 从线程状态转变过程我们也已经知道了线程通过调用start方法进行启动 直到run方法执行线程结束 今天我们就来详细的说说启动和终止线程的细节 OK 让我们开始今天的并发之旅吧 创建线程 在使用一个