java 线程的常用操作基本上都在java.lang.Thread 类中进行了定义,基础的操作可以下图进行概括:

接下来我们对每一项基本操作进行详细说明:

线程名称的设置和获取

在java编程规范中要求: 创建线程或线程池时,需要指定有意义的线程名称,方便出现bug的时候回溯.

在Thread 中可以通过两种方式设置线程名称,方式一是通过构造函数Thread(…)初始化设置线程名称,方式二是通过 setName()方法设置线程名称. 想要获取线程名称就只能通过getName()方法进行获取.

设置线程名称和获取线程名称的示例如下

package com.th.thread;import com.th.util.ThreadUtil;/*** @ClassName: ThreadNameDemo* @Description:* @Author: 唐欢* @Date: 2022/8/15 14:29* @Version 1.0*/
public class ThreadNameDemo {//每个线程的执行轮次private  static final  int MAX_TURN =3;// 异步执行目标类static class RunTarget implements  Runnable{//实现Runnable接口@Overridepublic void run() {//实现run()方法for (int turn=0 ;turn<MAX_TURN;turn++){ThreadUtil.sleepMilliSeconds(500);String cft= "["+Thread.currentThread().getName()+"] :"+"线程执行轮次:"+turn;System.out.println(cft);}}}public static void main(String[] args) {// 实例化Runnable 异步执行目标类RunTarget target = new RunTarget();//系统自动设置线程名称new Thread(target).start();//系统自动设置线程名称new Thread(target).start();//系统自动设置线程名称new Thread(target).start();new Thread(target,"手动命名线程-A").start();new Thread(target,"手动命名线程-B").start();ThreadUtil.sleepMilliSeconds(Integer.MAX_VALUE);}}

运行结果如下:

在设置线程名称的时候,需要注意一下三点:

  • 线程名称一般在启动线程钱设置(即调用start()方法前进行设置线程名称),但是也允许为运行的线程设置名称(通常请下不建议这么操作)
  • 运行两个Thread对象有相同的名称, 但是应该避免, 两个相同名称的线程会在出现bug的时候增加回溯难度,无法快速准确定位.
  • 如果程序没有为指定名称, 系统会自动为线程设置名称. 建议最好是自己设置一个能快速定位的线程名称.

线程的Sleep操作

sleep 的作用是让当前正在执行的线程休眠,让CPU 去执行其他的任务, 从线程状态说,就是从执行状态变成限时阻塞状态.sleep()方法会抛出InterruptExcetion 异常, sleep()示例如下:

package com.th.thread;import com.th.util.ThreadUtil;/*** @ClassName: SleepDemo* @Description:* @Author: 唐欢* @Date: 2022/8/15 15:27* @Version 1.0*/
public class SleepDemo {//睡眠时长5秒public  static final int SLEEP_GAP =5000;//睡眠次数,值稍微大些方便使用jstackpublic  static final int MAX_TURN =50;static  class SleepThread extends  Thread{static int threadSeqNumber =1;public  SleepThread(){super("sleepThread-"+threadSeqNumber);threadSeqNumber++;}@Overridepublic void run() {try{for (int i=1;i<MAX_TURN;i++){System.out.println(getName()+"睡眠轮次:"+i);Thread.sleep(SLEEP_GAP);}} catch (InterruptedException e) {System.out.println(getName()+"发生异常被中断");// e.printStackTrace();}System.out.println(getName()+"运行结束.");}}public static void main(String[] args) {for (int i=0; i<5;i++){Thread thread =new SleepThread();thread.start();}System.out.println(ThreadUtil.getCurThreadName()+"运行结束.");}
}

运行结果如下:

使用jstack 查看运行情况如下:

通过jstack指令输出可以看到在进行线程DUMP 的时间点,所创建的线程都处于TIMED_WAITING(sleeping)状态.

总结:当线程sleep时间结束后,不一定会立即得到执行, 因为此时CPU 有可能正在执行其他的任务,此时sleep结束的线程就进入就绪状态,等待分配CPU时间片以便有机会执行.

线程的interrupt 操作

Thread 的interrupt()方法的作用是将线程设置为中断状态.在以下两种情况下调用interrupt() 进行中断:

  • 如果此线程处于阻塞状态,就会立即退出阻塞,并抛出InterruptException异常,从而提早终结被阻塞状态.
  • 如果此线程 正在处于运行之中,线程就不受任何影响,继续运行,仅仅是线程的中断标记被设置为true.所以,程序可以在适当的位置通过调用isInterrupted()方法来查看自己是否被中断,并执行退出操作.

示例如下:

package com.th.thread;import com.th.util.ThreadUtil;/*** @ClassName: InterruptDemo* @Description:* @Author: 唐欢* @Date: 2022/8/16 15:34* @Version 1.0*/
public class InterruptDemo {// 睡眠时长public  static  final  int SLEEP_GAR =5000;//睡眠次数public  static final  int MAX_TURN =50;static  class SleepThread extends  Thread{static  int threadSeqNumber=1;public SleepThread(){super("sleepThread" + threadSeqNumber);threadSeqNumber++;}@Overridepublic void run() {System.out.println("["+Thread.currentThread().getName()+"] :"+getName()+"进入睡眠");// 线程sleep会try {Thread.sleep(SLEEP_GAR);} catch (InterruptedException e) {e.printStackTrace();System.out.println("["+Thread.currentThread().getName()+"] :"+getName()+"发生被异常打断");return;}System.out.println("["+Thread.currentThread().getName()+"] :"+getName()+"运行结束");}}public static void main(String[] args) {Thread thread1 = new SleepThread();thread1.start();Thread thread2 = new SleepThread();thread2.start();// 主线程等待2秒ThreadUtil.sleepSeconds(2);//打断线程1thread1.interrupt();// 主线程等待5秒ThreadUtil.sleepSeconds(5);//打断线程2,此时线程2已经终止thread2.interrupt();// 主线程等待1秒ThreadUtil.sleepSeconds(1);//打断线程2,此时线程2已经终止System.out.println("["+Thread.currentThread().getName()+"] :"+"程序运行结束");}
}

运行结果如下:

从运行结果可以看出,sleepThread1 大致sleep了2s后,被主线程打断,被打断的sleepThread1线程停止睡眠,并捕获到了InterruptException异常并抛出. sleepThread2 线程在sleep 7s后, 被主线程中断, 但是由于sleepThread2在被中断的时候已经执行结束了,所有对sleepThread2 没有产生实际性的影响.

线程的join操作

调用join()方法为Thread 线程合并,合并的本质是线程A 需要在合并点等待,一直等到线程B执行完成,或者等待超时. 举个例子, 依赖的线程A 调用被依赖的线程B的join()方法, 在执行流程上将被依赖的线程B 合并到依赖线程A,依赖线程A 等待被依赖线程B 执行完成后,依赖线程A 再继续执行.执行流程如下图所示:

调用join()方法示例:

package com.th.thread;/*** @ClassName: JoinDemo* @Description:* @Author: 唐欢* @Date: 2022/8/16 16:37* @Version 1.0*/
public class JoinDemo {// 睡眠时长public  static  final  int SLEEP_GAR =5000;//睡眠次数public  static final  int MAX_TURN =50;static  class SleepThread extends  Thread {static int threadSeqNumber = 1;public SleepThread() {super("sleepThread" + threadSeqNumber);threadSeqNumber++;}@Overridepublic void run() {System.out.println("["+Thread.currentThread().getName()+"] :"+getName()+"进入睡眠");// 线程sleep会try {Thread.sleep(SLEEP_GAR);} catch (InterruptedException e) {e.printStackTrace();System.out.println("["+Thread.currentThread().getName()+"] :"+getName()+"发生被异常打断");return;}System.out.println("["+Thread.currentThread().getName()+"] :"+getName()+"运行结束");}}public static void main(String[] args) {Thread threadA =new SleepThread();System.out.println("["+Thread.currentThread().getName()+"] :"+"启动threadA");threadA.start();try {//合并线程A,不限时threadA.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("["+Thread.currentThread().getName()+"] :"+"启动threadB");// 启动第二天线程,并且进行限时合并,等待时间为1秒Thread threadB = new SleepThread();threadB.start();try {//合并线程B,不限时threadB.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("["+Thread.currentThread().getName()+"] :"+"线程运行结束");}}

执行结果:

线程的yield操作

yield()方法的作用是让目前正在执行的线程放弃当前的执行,让出cpu的执行权限,使得cpu去执行其他的线程.处于让步状态的JVM 层面的线程状态仍然是RUNNABLE状态,从执行状态变成了就绪状态.示例如下:

package com.th.thread;import com.th.util.Print;import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.LockSupport;/*** @ClassName: YieldDemo* @Description:* @Author: 唐欢* @Date: 2022/8/16 17:03* @Version 1.0*/
public class YieldDemo {//执行次数public static final int MAX_TURN = 100;//执行编号public static AtomicInteger index = new AtomicInteger(0);// 记录线程的执行次数private static Map<String, AtomicInteger> metric = new HashMap<>();//输出线程的执行次数private static void printMetric() {System.out.println("["+Thread.currentThread().getName()+"] :"+"metric = " + metric);}static class YieldThread extends Thread {static int threadSeqNumber = 1;public YieldThread() {super("YieldThread-" + threadSeqNumber);threadSeqNumber++;metric.put(this.getName(), new AtomicInteger(0));}@Overridepublic void run() {for (int i = 1; i < MAX_TURN && index.get() < MAX_TURN; i++) {System.out.println("["+Thread.currentThread().getName()+"] :"+"线程优先级:" + getPriority());index.incrementAndGet();metric.get(this.getName()).incrementAndGet();if (i % 2 == 0) {//让步:出让执行的权限Thread.yield();}}//输出线程的执行次数printMetric();System.out.println("["+Thread.currentThread().getName()+"] :"+getName() + " 运行结束.");}}public static void main(String args[]) {Thread thread1 = new YieldThread();thread1.setPriority(Thread.MAX_PRIORITY);Thread thread2 = new YieldThread();thread2.setPriority(Thread.MIN_PRIORITY);Print.tco("启动线程.");System.out.println("["+Thread.currentThread().getName()+"] :"+"启动线程.");thread1.start();thread2.start();LockSupport.parkNanos(100*1000L*1000L*1000L);}
}

执行结果如下:

从结果可以看出,程序一共启动了两个让步演示线程, 两个线程妹执行两次操作就让出cpu.但是两个线程的的优先级不同, 优先级高的执行次数比优先级低的执行次数多很多. 从而可得出,线程调用yield之后,操作系统在重新进行线程调度时偏向于执行机会让给优先级较高的线程.

yield 和sleep有些容易混淆的地方,但是他们之间还是存在着本质区别的:
(1)sleep会导致当前线程暂停指定的时间,没有CPU时间片的消耗。
(2)yield 只是对CPU调度器的一个提示,如果CPU调度器没有忽略这个提示,它会导致线程上下文的切换。
(3)sleep会使线程短暂block,会在给定时间内释放CPU资源。
(4)yield会使RUNNING状态的Thread进入RUNNABLE状态(如果CPU 调度器没有忽略这个提示的话)
(5)sleep几乎百分之百地完成给定时间的休眠,而yield的提示并不一定担保。
(6)一个线程sleep 另一个线程调用interrupt会捕获到中断信号,而yield则不会。

总结:

  • yield仅能使一个线程从运行状态转到就绪状态,而不是阻塞状态。
  • yield不能保证使得当前正在运行的线程迅速转换到就绪状态。
  • 即使完成了迅速切换,系统通过线程调度机制从所有就绪线程中挑选下一个执行线程时,就绪的线程有可能被选中,也有可能不被选中,其调度的过程受到其他因素的影响。

线程的daemo操作

java线程分为守护线程(后台线程)和用户线程, 在开发中, 实例属性daemon =false ,默认情况是用户线程,可以通过调用setDaemon()方法设置,true是守护线程, false则为用户线程, 还可以通过isDaemon 获取线程的守护状态.示例如下:

package com.th.thread;import com.th.util.Print;import java.util.concurrent.locks.LockSupport;/*** @ClassName: DaemonDemo* @Description:* @Author: 唐欢* @Date: 2022/8/16 17:14* @Version 1.0*/
public class DaemonDemo {public static final int SLEEP_GAP = 500; //每一轮的睡眠时长public static final int MAX_TURN = 4; //用户线程执行轮次//守护线程实现类static class DaemonThread extends Thread {public DaemonThread() {super("daemonThread");}@Overridepublic void run() {System.out.println("[" + Thread.currentThread().getName() + "]" + ":" +"--daemon线程开始.");for (int i = 1; ; i++) {System.out.println("[" + Thread.currentThread().getName() + "]" + ":" +"--轮次:" + i + "--守护状态为:" + isDaemon());// 线程睡眠一会LockSupport.parkNanos(SLEEP_GAP * 1000L * 1000L);}}}public static void main(String args[]) throws InterruptedException {Thread daemonThread = new DaemonThread();daemonThread.setDaemon(true);daemonThread.start();Thread userThread = new Thread(() ->{System.out.println("[" + Thread.currentThread().getName() + "]" + ":" +">>用户线程开始.");for (int i = 1; i <= MAX_TURN; i++) {System.out.println("[" + Thread.currentThread().getName() + "]" + ":" +">>轮次:" + i + " -守护状态为:" + Thread.currentThread().isDaemon());LockSupport.parkNanos(SLEEP_GAP * 1000L * 1000L);}System.out.println("[" + Thread.currentThread().getName() + "]" + ":" +">>用户线程结束.");}, "userThread");userThread.start();//主线程合入userThread,等待userThread执行完成
//        userThread.join();System.out.println("[" + Thread.currentThread().getName() + "]" + ":" +" 守护状态为:" + Thread.currentThread().isDaemon());System.out.println("[" + Thread.currentThread().getName() + "]" + ":" +"运行结束.");}
}

运行结果如下:

从运行的结果可以看出: main 线程也是一条用户线程.main 线程在创建和启动了daemonThread 和userThread 后,就提前结束了,虽然main线程结束了,但是两条线程还在继续执行,其中就有一条是用户线程,所以进程还不能结束. 当剩下的一条用户线程userThread的run()方法执行完成后,userThread 线程执行结束.这是,所有用户线程执行已经完成,jvm进程就随之退出了. 在JVM退出时,守护线程daemonThread 还没有结束,还在继续死循环的执行中,而jvm并不管这些,强行终止了所有守护线程的执行.

从以上的示例我们可以得出这样一个结论: 用户线程和JVM 进程是主动关系,如果用户线程全部终止,JVM虚拟机进程也随之终止; 守护线程和JVM进程是被动关系,如果JVM进程终止,所有的守护线程也随之终止,下图清晰的表达了它们之间的关系:

在开发过程中, 使用守护线程需要特别注意一下几点:

  • 守护线程必须在启动前(即调用start()之前)将其设置为守护状态为true. 启动之后就不能再将用户线程设置为守护线程,否则会抛出InterruptException异常.
  • 守护线程存在被JVM强行终止的风险,所以在守护线程中尽量不要去访问系统资源. 守护线程被强行终止时,可能会引发系统资源操作不负责的中断, 从而导致资源不可逆的损坏.
  • 守护线程创建的线程也是守护线程.创建完成后可以调用setDaemon(false)设置为用户线程.

到此为止, Thread线程的的基本操作就介绍完成了,你学会了吗?

通过此文让你全面了解Thread线程的基本操作相关推荐

  1. 在Android中使用Handler和Thread线程执行后台操作

    在 Android中使用Handler和Thread线程执行后台操作 对于线程的控制,我们将介绍一个 Handler类,使用该类可以对运行在不同线程中的多个任务进行排队,并使用Message和Runn ...

  2. Android中使用Thread线程出现的问题

    很多初入Android或Java开发的新手对Thread.Looper.Handler和Message仍然比较迷惑,衍生的有HandlerThread.java.util.concurrent.Tas ...

  3. android java thread_Android中断并重启一个Thread线程的简单方法

    这里简单的总结下(大概思路,没调试,可能会有错!): MyThread.java pulbic class MyThread implemets Thread{ @overide public voi ...

  4. [Android]Thread线程入门3--多线程

    经过 [Android]Thread线程入门1 和[Android]Thread线程入门2 的学习,我们对线程有了简单的了解.在实际应用中,一般都会用到多线程.很少像前面的例子这么简单.那么如何实现多 ...

  5. 19.Qt中Thread线程中创建QTcpSocket

    Thread线程中创建QTcpSocket 本文承接上一篇博文,Qt线程创建,本文记录在线程中创建socket 套接字,连接服务器进行编程. /**************************** ...

  6. Thread 线程基础之-线程相关知识

    线程的优先级 设置或者获得当前线程的优先级: using System;using System.Collections.Generic;using System.Text;using System. ...

  7. java thread 线程_Java Thread类简述

    今天我们来看下java.lang.Thread这个类. 在学习Thread类之前,先看下线程相关知识:线程的几种状态.上下文切换,然后介绍Thread类中的方法的具体使用. 1.线程的状态 线程从创建 ...

  8. Qt使用 std::thread 线程插入数据到 QTableWidget

    Qt使用 std::thread 线程插入数据到 QTableWidget中 一.实现效果 二.主要代码 1. ThreadTable.h 头文件 2. ThreadTable.cpp 源文件 3. ...

  9. Thread线程从零认识到深层理解——初识

    线程系列目录 Thread线程从零认识到深层理解--初识 Thread线程从零认识到深层理解--六大状态 Thread线程从零认识到深层理解--wait()与notify() Thread线程从零认识 ...

最新文章

  1. SAP CAR 的主要优势
  2. 核心动画的接触点滴(五)
  3. hibernate的一种报错
  4. prometheus rfc3339 php date 转换
  5. 常用来进行钢结构节点输出的软件是什么_Revit中如何处理钢结构节点连接
  6. body标签下莫名奇妙多了一行空行,原来是编码的问题
  7. 静态和动态include
  8. 幼儿园 c语言,【资源学习】c语言程序代码,登录幼儿园200个小朋友的数据
  9. html没有代码补全 vscode_借助clangd实现VSCode C++代码补全
  10. Android 任务栈空间,【Android】任务和返回栈(tasks and back stack)
  11. 三个linux系统共存,修改默认启动
  12. Eclipse 项目导入 Android Studio 导致的乱码问题解决
  13. 工科数学分析序言及索引(不断更新中)
  14. c语言两矩阵相乘算法,用c语言实现两个矩阵相乘怎么做?
  15. Netflix-Eureka服务注册与发现说明文档
  16. stylegan2报错“undefined symbol: _ZN10tensorflow12OpDefBuilder6OutputESs”的解决方案
  17. cadence virtuoso画版图提示LUP.6错误
  18. 求极值函数-MATLAB
  19. Spoon系列-概要
  20. 程序UI设计——工业软件UI 设计心得

热门文章

  1. java与php的优缺点_java和php的发展前景以及优劣势
  2. 基于微信小程序的教学教务管理系统(后台Java+SSM+MySQL)
  3. NVIDIA控制面板不能用的解决方法(先确保显卡硬件正常)
  4. Windows Media Player 与 ActiveMove Window遇到的相关问题
  5. 【图像识别】基于二维条形码识别matlab 源码含GUI
  6. 掌上网咖是云服务器吗,手机玩云顶之弈?掌上网咖云电脑打开即玩
  7. Win32汇编WG系列教程1——《植物大战僵尸5211314》
  8. 百度地图BMap和BMapGL的区别
  9. SQLServer创建链接服务器
  10. 虚拟偶像是未来趋势吗?