读书笔记-Java并发编程的艺术-第4章(Java并发编程基础)-第2节(启动和终止线程)
文章目录
- 4.2 启动和终止线程
- 4.2.1 构造线程
- 4.2.2 启动线程
- 4.2.3 理解中断
- 4.2.4 过期的suspend()、resume()和stop()
- 4.2.5 安全地终止线程
4.2 启动和终止线程
在前面章节的示例中通过调用线程的start()方法进行启动,随着run()方法的执行完毕,线程也随之终止,大家对此一定不会陌生,下面将详细介绍线程的启动和终止。
4.2.1 构造线程
在运行线程之前首先要构造一个线程对象,线程对象在构造的时候需要提供线程所需要的属性,如线程所属的线程组、线程优先级、是否是Daemon线程等信息。以下代码摘自java.lang.Thread 中对线程进行初始化的部分。
/*** Initializes a Thread.** @param g the Thread group* @param target the object whose run() method gets called* @param name the name of the new Thread* @param stackSize the desired stack size for the new thread, or* zero to indicate that this parameter is to be ignored.* @param acc the AccessControlContext to inherit, or* AccessController.getContext() if null* @param inheritThreadLocals if {@code true}, inherit initial values for* inheritable thread-locals from the constructing thread*/private void init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc,boolean inheritThreadLocals) {if (name == null) {throw new NullPointerException("name cannot be null");}this.name = name;Thread parent = currentThread();SecurityManager security = System.getSecurityManager();if (g == null) {/* Determine if it's an applet or not *//* If there is a security manager, ask the security managerwhat to do. */if (security != null) {g = security.getThreadGroup();}/* If the security doesn't have a strong opinion of the matteruse the parent thread group. */if (g == null) {g = parent.getThreadGroup();}}/* checkAccess regardless of whether or not threadgroup isexplicitly passed in. */g.checkAccess();/** Do we have the required permissions?*/if (security != null) {if (isCCLOverridden(getClass())) {security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);}}g.addUnstarted();this.group = g;this.daemon = parent.isDaemon();this.priority = parent.getPriority();if (security == null || isCCLOverridden(parent.getClass()))this.contextClassLoader = parent.getContextClassLoader();elsethis.contextClassLoader = parent.contextClassLoader;this.inheritedAccessControlContext =acc != null ? acc : AccessController.getContext();this.target = target;setPriority(priority);if (inheritThreadLocals && parent.inheritableThreadLocals != null)this.inheritableThreadLocals =ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);/* Stash the specified stack size in case the VM cares */this.stackSize = stackSize;/* Set thread ID */tid = nextThreadID();}
在上述过程中,一个新构造的线程对象是由其parent线程来进行空间分配的,而child线程继承丁parent是否为Daemon、优先级和加载资源的contextClassLoader以及可继承的ThreadLocal、同时还会分配一个唯一的ID来标识这个 child线程。至此,一个能够运行的线程对象就初始化好了,在堆内存中等待着运行。
4.2.2 启动线程
线程对象在初始化完成之后,调用start()方法就可以启动这个线程。线程start()方法含义是:当前线程(即parent线程)同步告知Java虚拟机,只要线程规划器空闲,应立即动调用 start()方法的线程。
注意:启动一个线程前,最好为这个线程设置线程名称,因为这样在使用jstack分析程序者进行问题排查时,就会给开发人员提供一些提示,自定义的线程最好能够起个名字。
4.2.3 理解中断
中断可以理解为线程的一个标识位属性,它表示一个运行中的线程是否被其他线程进行了中断操作。中断好比其他线程对该线程打了个招呼,其他线程通过调用该线程的interrupt()方法对其进行中断操作。
线程通过检查自身是否被中断来进行响应,线程通过方法isInterrupted()来进行判断否被中断,也可以调用静态方法Thread.interupted()对当前线程的中断标识位进行复位。如果该线程已经处于终结状态,即使该线程被中断过,在调用该线程对象的isInterrupted()时依旧会返回 false。
从Java的API中可以看到,许多声明抛出InterruptedException的方法(例如Thread.sleep(long millis)方法),这些方法InterruptedException之前,Java虚拟机会先将该线程的中断标识位清除,然后抛出InterruptedException,此时调用isInterrupted()方法将会返回false。
在下面代码中,首先创建了两个线程,SleepThread和BusyThread,前者不停地睡眠,后者一直运行,然后对这两个线程分别进行中断操作,观察二者的中断标识位。
import java.util.concurrent.TimeUnit;public class Interrupted {public static void main(String[] args) throws InterruptedException {// sleepThread不停地尝试睡眠Thread sleepThread = new Thread(new SleepRunner(), "SleepThread");sleepThread.setDaemon(true);// busyThread不停地运行Thread busyThread = new Thread(new BusyRunner(), "BusyThread");busyThread.setDaemon(true);sleepThread.start();busyThread.start();// 休眠5秒,让sleepThread和busyThread充分运行TimeUnit.SECONDS.sleep(5);sleepThread.interrupt();busyThread.interrupt();System.out.println("SleepThread interrupted is " + sleepThread.isInterrupted());System.out.println("BusyThread interrupted is " + busyThread.isInterrupted());// 防止sleepThread和busyThread立刻退出SleepUtils.second(2);}static class SleepRunner implements Runnable {@Overridepublic void run() {while (true) {SleepUtils.second(10);}}}static class BusyRunner implements Runnable {@Overridepublic void run() {while (true) {}}}
}
import java.util.concurrent.TimeUnit;public class SleepUtils {public static final void second(long seconds) {try {TimeUnit.SECONDS.sleep(seconds);} catch (InterruptedException e) {e.printStackTrace();}}
}
打印:
SleepThread interrupted is false
BusyThread interrupted is true
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at java.lang.Thread.sleep(Thread.java:340)
at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
at com.xin.demo.threaddemo.bookdemo.SleepUtils.second(SleepUtils.java:8)
at com.xin.demo.threaddemo.bookdemo.Interrupted$SleepRunner.run(Interrupted.java:32)
at java.lang.Thread.run(Thread.java:748)
从结果可以看出,抛出InterruptedException的线程SleepThread,其中断标识位被清除了,而一直忙碌运作的线程BusyThread,中断标识位没有被清除。
4.2.4 过期的suspend()、resume()和stop()
大家对于CD机肯定不会陌生,如果把它播放音乐比作一个线程的运作,那么对音乐播放做出的暂停、恢复和停止操作对应在线程Thread的API就是suspend()、resume()和stop()。
在下面代码中,创建了一个线程PrintThread,它以1秒的频率进行打印,而主线程对其进行暂停、恢复和停止操作。
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;public class Deprecated {public static void main(String[] args) throws Exception {DateFormat format = new SimpleDateFormat("HH:mm:ss");Thread printThread = new Thread(new Runner(), "printThread");printThread.setDaemon(true);printThread.start();TimeUnit.SECONDS.sleep(3);// 将PrintThread进行暂停,输出内容工作停止printThread.suspend();System.out.println("main suspend PrintThread at " + format.format(new Date()));TimeUnit.SECONDS.sleep(3);// 将PrintThread进行恢复,输出内容继续printThread.resume();System.out.println("main resume PrintThread at " + format.format(new Date()));TimeUnit.SECONDS.sleep(3);//将PrintThread进行终止,输出内容停止printThread.stop();System.out.println("main stop PrintThread at " + format.format(new Date()));TimeUnit.SECONDS.sleep(3);}static class Runner implements Runnable {@Overridepublic void run() {DateFormat format = new SimpleDateFormat("HH:mm:ss");while (true) {System.out.println(Thread.currentThread().getName() + " Run at " + format.format(new Date()));SleepUtils.second(1);}}}
}
打印:
printThread Run at 20:46:36
printThread Run at 20:46:37
printThread Run at 20:46:38
main suspend PrintThread at 20:46:39
main resume PrintThread at 20:46:42
printThread Run at 20:46:42
printThread Run at 20:46:43
printThread Run at 20:46:44
main stop PrintThread at 20:46:45
在执行过程中,PrintThread运行了3秒,随后被暂停,3秒后恢复,最后经过3秒被终止。通过示例的输出可以看到,suspend()、resume()和stop()方法完成了线程的暂停、恢复和终止工作,而且非常“人性化”。
不建议使用的原因主要有:以suspend()方法为例,在调用后,线程不会释放已经占有的资源(比如锁),而是占有着资源进入睡眠状态,这样容易引发死锁问题。同样,stop()方法在终结一个线程时不会保证线程的资源正常释放。通常是没有给予线程完成资源释放工作的机会,因此会导致程序可能工作在不确定状态下。
注意:正因为 suspend()、resume()和stop()方法带来的副作用,这些方法才被标注为不建议使用的过期方法,而暂停和恢复操作可以用后面提到的等待/通知机制来替代。
4.2.5 安全地终止线程
在4.2.3节中提到的中断状态是线程的一个标识位,而中断操作是一种简便的线程间交互方式,而这种交互方式最适合用来取消或停止任务。除了中断以外,还可以利用一个boolean 变量来控制是否需要停止任务并终止该线程。
在下面代码中,创建了一个线程CountThread,它不断地进行变量累加,面主线程尝试对其进行中断操作和停止操作。
import java.util.concurrent.TimeUnit;public class ShutDown {public static void main(String[] args) throws InterruptedException {Runner one = new Runner();Thread countThread = new Thread(one, "CountThread");countThread.start();// 睡眠1秒,main线程对CountThread进行中断,使CountThread能够感知中断而结束TimeUnit.SECONDS.sleep(1);countThread.interrupt();Runner two = new Runner();countThread = new Thread(two, "CountThread");countThread.start();// 睡眠1秒,main线程对Runner two进行取消,使CountThread能够感知on为false而结束TimeUnit.SECONDS.sleep(1);two.cancel();}private static class Runner implements Runnable {private long i;private volatile boolean on = true;@Overridepublic void run() {while (on && !Thread.currentThread().isInterrupted()) {i++;}System.out.println("Count i = " + i);}public void cancel() {on = false;}}
}
打印:
Count i = 198875747
Count i = 196373773
示例在执行过程中,main线程通过中断操作和cancel()方法均可使CountThread得以终止。这种通过标识位或者中断操作的方式能够使线程在终止时有机会去清理资源,而不是武断地将线程停止,因此这种终止线程的做法显得更加安全和优雅。
相关文章:
读书笔记-Java并发编程的艺术-第4章(Java并发编程基础)-第2节(启动和终止线程)
文章目录 4.2 启动和终止线程4.2.1 构造线程4.2.2 启动线程4.2.3 理解中断4.2.4 过期的suspend()、resume()和stop()4.2.5 安全地终止线程 4.2 启动和终止线程 在前面章节的示例中通过调用线程的start()方法进行启动,随着run()方法的执行完毕,线程也随之…...
通俗大白话理解Docker
什么是Docker Docker本质上是一种容器化技术,用于将应用程序及其所有依赖打包到一个标准化的单元中。这些单元(容器)可以在任何运行Docker的机器上运行。每个容器是相互隔离的,具有自己的文件系统、网络和进程空间。 以下是大白话…...
题解:CF1981C(Turtle and an Incomplete Sequence)
题解:CF1981C(Turtle and an Incomplete Sequence) Part 1:题意理解 地址链接:CF、洛谷。题面翻译:给定一个长度为 n n n 的序列 a a a,其中有一些元素未知,用 − 1 -1 −1 表示…...

Swift 中强大的 Key Paths(键路径)机制趣谈(上)
概览 小伙伴们可能不知道:在 Swift 语言中隐藏着大量看似“其貌不扬”实则却让秃头码农们“高世骇俗”,堪称卧虎藏龙的各种秘技。 其中,有一枚“不起眼”的小家伙称之为键路径(Key Paths)。如若将其善加利用ÿ…...

(十二)纹理和采样
纹理 在绘制三角形的过程中,将图片贴到三角形上进行显示的过程,就是纹理贴图的过程 uv坐标 如果如果图片尺寸和实际贴图尺寸不一致,就会导致像素不够用了的问题 纹理与采样 纹理对象(Texture):在GPU端,用来以一…...

QT创建地理信息shp文件编辑器shp_editor
空闲之余创建一个简单的矢量shp文件编辑器,加深对shp文件的理解。 一、启动程序 二、打开shp文件 三、显示shp文件的几何图形 四、双击右边表格中的feature,主窗体显示选中feature的各个节点。 五、鼠标在主窗体中选中feature的节点,按鼠标左…...

解析Kotlin中扩展函数与扩展属性【笔记摘要】
1.扩展函数 1.1 作用域:扩展函数写的位置不同,作用域就也不同 扩展函数可以写成顶层函数(Top-level Function),此时它只属于它所在的 package。这样你就能在任何类里使用它: package com.rengwuxianfun …...

【Java学习笔记】java图形界面编程
在前面的章节中,我们开发运行的应用程序都没有图形界面,但是很多应用软件,如Windows下的Office办公软件、扑克牌接龙游戏软件、企业进销存ERP系统等,都有很漂亮的图形界面。素以需要我们开发具有图形界面的软件。 Java图形界面编程…...

STM32入门笔记(03): ADC(SPL库函数版)(2)
A/D转换的常用技术有逐次逼近式、双积分式、并行式和跟踪比较式等。目前用的较多的是前3种。 A/D转换器的主要技术指标 转换时间 分辨率 例如,8位A/D转换器的数字输出量的变化范围为0~255,当输入电压的满刻度为5V时,数字量每变化…...

2024年7月2日 (周二) 叶子游戏新闻
老板键工具来唤去: 它可以为常用程序自定义快捷键,实现一键唤起、一键隐藏的 Windows 工具,并且支持窗口动态绑定快捷键(无需设置自动实现)。 卸载工具 HiBitUninstaller: Windows上的软件卸载工具 经典名作30周年新篇《恐怖惊魂夜…...
如何使用Spring Boot Profiles进行环境配置管理
如何使用Spring Boot Profiles进行环境配置管理 大家好,我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编,也是冬天不穿秋裤,天冷也要风度的程序猿!今天我们将深入探讨如何利用Spring Boot Profiles来管理不同环境…...
Java错题归纳(二)
1、若有如下接口A的定义,下列哪些类下确实现了该接口:C interface A { void method1(int i); void method2(int j); } A class B implements A{ void method1( ) { } void method2( ) { } } B class B implements A { void method1(int i ) { }…...
Grafana面试题精选和参考答案
目录 Grafana是什么以及它的主要应用场景 Grafana支持的数据源 Grafana的体系结构及主要组件 Grafana如何实现数据的可视化和监控 Grafana支持的图表类型 如何在Grafana中创建和编辑仪表盘 Grafana的查询编辑器功能 Grafana支持的认证方式 Grafana的性能调优建议 Gra…...

Node版本管理工具 fnm 安装使用
fnm 是一个基于 Rust 开发的 Node 版本管理工具,它的目标是提供一个快速、简单且可靠的方式来管理 Node.js 的不同版本。同时,它是跨平台的,支持 macOS、Linux、Windows。🚀 Fast and simple Node.js version manager, built in R…...

vector模拟实现【C++】
文章目录 全部的实现代码放在了文章末尾准备工作包含头文件定义命名空间和类类的成员变量 迭代器迭代器获取函数 构造函数默认构造使用n个值构造迭代器区间构造解决迭代器区间构造和用n个值构造的冲突拷贝构造 析构函数swap【交换函数】赋值运算符重载emptysize和capacityopera…...
《每天5分钟用Flask搭建一个管理系统》第11章:测试与部署
第11章:测试与部署 11.1 测试的重要性 测试是确保应用质量和可靠性的关键步骤。它帮助开发者发现和修复错误,验证功能按预期工作。 11.2 Flask测试客户端的使用 Flask提供了一个测试客户端,可以在开发过程中模拟请求并测试应用的响应。 …...

Landsat数据从Collection1更改为Collection2
目录 问题解决 问题 需要注意!您使用的是废弃的陆地卫星数据集。为确保功能持续,请在2024年7月1日前更新。 在使用一些以前的代码时会遇到报错,因为代码里面用的是老的数据集 解决 对于地表反射率SR,需要在name中,将C01换为C02&…...
《每天5分钟用Flask搭建一个管理系统》第12章:安全性
第12章:安全性 12.1 Web应用的安全威胁 Web应用面临的安全威胁包括但不限于跨站脚本攻击(XSS)、SQL注入、跨站请求伪造(CSRF)、不安全的直接对象引用(IDOR)等。 12.2 Flask-Talisman扩展的使…...

Unity之创建与导出PDF
内容将会持续更新,有错误的地方欢迎指正,谢谢! Unity之创建与导出PDF TechX 坚持将创新的科技带给世界! 拥有更好的学习体验 —— 不断努力,不断进步,不断探索 TechX —— 心探索、心进取! 助力快速…...
【Android面试八股文】优化View层次过深问题,选择哪个布局比较好?
优化深层次View层次结构的问题,选择合适的布局方式是至关重要的。以下是几点建议: 使用ConstraintLayout:ConstraintLayout是Android开发中推荐的布局,能够有效减少嵌套,提高布局性能。相比RelativeLayout,…...

Docker 离线安装指南
参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性,不同版本的Docker对内核版本有不同要求。例如,Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本,Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...

深入浅出Asp.Net Core MVC应用开发系列-AspNetCore中的日志记录
ASP.NET Core 是一个跨平台的开源框架,用于在 Windows、macOS 或 Linux 上生成基于云的新式 Web 应用。 ASP.NET Core 中的日志记录 .NET 通过 ILogger API 支持高性能结构化日志记录,以帮助监视应用程序行为和诊断问题。 可以通过配置不同的记录提供程…...

日语AI面试高效通关秘籍:专业解读与青柚面试智能助攻
在如今就业市场竞争日益激烈的背景下,越来越多的求职者将目光投向了日本及中日双语岗位。但是,一场日语面试往往让许多人感到步履维艰。你是否也曾因为面试官抛出的“刁钻问题”而心生畏惧?面对生疏的日语交流环境,即便提前恶补了…...
云计算——弹性云计算器(ECS)
弹性云服务器:ECS 概述 云计算重构了ICT系统,云计算平台厂商推出使得厂家能够主要关注应用管理而非平台管理的云平台,包含如下主要概念。 ECS(Elastic Cloud Server):即弹性云服务器,是云计算…...

AI Agent与Agentic AI:原理、应用、挑战与未来展望
文章目录 一、引言二、AI Agent与Agentic AI的兴起2.1 技术契机与生态成熟2.2 Agent的定义与特征2.3 Agent的发展历程 三、AI Agent的核心技术栈解密3.1 感知模块代码示例:使用Python和OpenCV进行图像识别 3.2 认知与决策模块代码示例:使用OpenAI GPT-3进…...

渗透实战PortSwigger靶场-XSS Lab 14:大多数标签和属性被阻止
<script>标签被拦截 我们需要把全部可用的 tag 和 event 进行暴力破解 XSS cheat sheet: https://portswigger.net/web-security/cross-site-scripting/cheat-sheet 通过爆破发现body可以用 再把全部 events 放进去爆破 这些 event 全部可用 <body onres…...

学校招生小程序源码介绍
基于ThinkPHPFastAdminUniApp开发的学校招生小程序源码,专为学校招生场景量身打造,功能实用且操作便捷。 从技术架构来看,ThinkPHP提供稳定可靠的后台服务,FastAdmin加速开发流程,UniApp则保障小程序在多端有良好的兼…...
基于matlab策略迭代和值迭代法的动态规划
经典的基于策略迭代和值迭代法的动态规划matlab代码,实现机器人的最优运输 Dynamic-Programming-master/Environment.pdf , 104724 Dynamic-Programming-master/README.md , 506 Dynamic-Programming-master/generalizedPolicyIteration.m , 1970 Dynamic-Programm…...
代理篇12|深入理解 Vite中的Proxy接口代理配置
在前端开发中,常常会遇到 跨域请求接口 的情况。为了解决这个问题,Vite 和 Webpack 都提供了 proxy 代理功能,用于将本地开发请求转发到后端服务器。 什么是代理(proxy)? 代理是在开发过程中,前端项目通过开发服务器,将指定的请求“转发”到真实的后端服务器,从而绕…...
Pinocchio 库详解及其在足式机器人上的应用
Pinocchio 库详解及其在足式机器人上的应用 Pinocchio (Pinocchio is not only a nose) 是一个开源的 C 库,专门用于快速计算机器人模型的正向运动学、逆向运动学、雅可比矩阵、动力学和动力学导数。它主要关注效率和准确性,并提供了一个通用的框架&…...