java线程之Thread类的基本用法

Thread类的基本用法

  • 1. Thread类的构造方法
  • 2. Thread的几个常见属性
    • 常见属性
    • 线程中断
    • 等待一个线程

小鱼在上一篇博客详细的讲解了如何创建线程,java使用Thread类来创建多线程,但是对于好多没有相关经验的人来说,比较不容易理解的地方在于操作系统调度的执行过程. 我们通过下面代码举例:

    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            System.out.println("hello t");
        });
        //调用start(),创建一个新的线程
        t.start();
 
        System.out.println("hello main");
    }

我们的java在启动时,会创建一个主线程,此时主线程的名字就是main,接下来我们执行main线程里面的一条条代码了,当我们执行到t.start()时,系统就会自动帮我们创建一个新的线程,并且会自动帮我们调用该线程中的run()方法,此时的run()方法就可以认为是个入口,一些重要的实现逻辑将都在里面实现.

我们通过上述代码举例:

当我们实例一个thread并且调用start()方法之后, main 线程和 t 线程就是(并发+并行)的过程,由于多线程有一个万恶之源–抢占机制,所以就会导致我们无法预测到程序是优先执行"hello t" 还是 “hello main”,并且对于谁先结束线程的任务,我们也是不确定的,可能是main线程优先结束,也可能是t线程优先结束.

在这里插入图片描述

创建线程是看start的顺序,但是具体的线程对应的任务什么时候执行,要看系统调度器。

1. Thread类的构造方法

方法说明
Thread()创建线程对象
Thread(Runnable target)使用Runnable对象创建线程对象
Thread(String name)创建线程对象并命名
Thread(Runnable target,String name)使用Runnable对象创建线程对象,并命名
Thread(ThreadGroup group,Runnable target)线程可以被用来分组管理,分好的组即为线程组

关于构造方法的讲解在这篇文章中详细的讲到了,包括如何创建一个线程,以及创建线程需要注意的事项都在这里一一列举了出来多线程的创建及其注意事项

2. Thread的几个常见属性

属性获取方法
IDgetId()
名称getName()
状态getState()
优先级getPriority()
是否后台线程isDaemon()
是否存活isAlive()
是否被中断isInterrupted()

常见属性

(1) getid() : ID表示线程的身份.就类似于我们的身份证一样,是独一无二的.这里的方法获取到的是线程在JVM中的身份标识,线程的标识有好多个,在内核的PCb上有标识,在用户态线程数据库中也有标识(操作系统的线程库)

(2) getName() : 获取当前线程的名字,多用于调试时使用.

(3) getState() :获取当前线程的状态,状态表示线程当前所属的一个情况.

(4) getPriority() : 优先级高的线程理论上来说更容易被调度到,但实际上并没有什么区别.大概可以理解为,你建议我不要晚睡,但也仅仅是建议,我可以不听.

(5) isDaemon() :daemon称为"守护线程",也可以称为后台线程,如果是后台线程就会返回true,前台线程则是false,我们可以这么理解后台线程,当我们手机打开qq这个app时,此时qq就是前台运行,但是由于我们后续可能会切换到快手app,此时qq就来到后台运行了.

一个线程创建出现默认是前台线程,前台线程会阻止进程的退出,进程只有在所有的前台线程都执行完之后才可以退出,但是对于后台进程,并不会阻止进程的结束.

JVM会在一个进程的所有非后台进程结束后,才会结束运行。

(6)isAlive() : 是否存活,即run()方法是否结束。

public class Demo4 {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("t 线程正在运行");
            }
        });
        System.out.println("t线程是否存活 "+t.isAlive());
        t.start();
        System.out.println("t线程是否存活 "+t.isAlive());

        try {
            Thread.sleep(1000);
            System.out.println("t线程是否存活 "+t.isAlive());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}

如果 t 的run还没跑,isAlive就为false;
如果 t 的run正在跑,isAlive就为true;
如果 t 的run跑完了,isAlive就为false;

执行结果:

在这里插入图片描述

此时我们将上述涉及到的属性的状态打印出来:

在这里插入图片描述

使用setDaemon(布尔值)方法设置一个线程为后台,true表示后台线程。

class MyThread1 extends Thread{
    @Override
    public void run() {
        System.out.println("hello  t");
    }
}
public class Demo1 {
    public static void main(String[] args) {
        Thread t = new MyThread1();
        t.start();

        System.out.println("hello main");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //设置为后台线程
        t.setDaemon(true);
        System.out.println("是否是后台线程 "+ t.isDaemon());
    }
}

执行结果:

在这里插入图片描述
上述操作都是获取到的一瞬间的状态,不是持续的状态。

start()和run()方法的区别:

当我们调用start()这个方法时,创建一个新的线程(有一个向系统创建一个线程的这么一个动作),此时这个线程会由系统自动调用run()方法(入口方法).并且这个run()方法是在这个新建的线程中运行的.
但是如果是程序员手动调用run()方法的话,并不会新建一个线程,并且该方法是在调用这个方法的线程中执行的.

获取当前线程的引用:

方法说明
public static Thread currentThread();返回当前线程对象的引用
 public static void main(String[] args) {
 		//获取当前线程的引用
        Thread t = Thread.currentThread();
        //通过当前线程的引用调用当前线程的名字
        System.out.println("当前线程名字 : "+t.getName());
    }

执行结果:
在这里插入图片描述

休眠当前线程:

方法说明
public static void sleep(long millis) throws InterruptedException休眠当前线程 millis毫秒
public static void sleep(long millis, int nanos) throwsInterruptedException可以更高精度的休眠
   public static void main(String[] args) {
        Thread t = new Thread(()->{
            System.out.println("hello t");
        });
        t.start();
        try {
            //睡眠1000ms,也可以称为阻塞1000ms
             Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("hello main");
    }

睡眠的状态就相当于这个线程被阻塞了,阻塞的时间由里面的参数决定,在这个线程阻塞的时间并不影响其他线程执行,可以理解为~我们在打游戏,打着打着未成年时间限制弹了出来,这时候就要求我们停止打游戏这个行为,只有等这个等待时间过去之后才可以继续打游戏.

:等待的过程中什么都不能做,相当于被定身了一样!!!

线程中断

中断线程,就是让线程尽快的把入口方法(run方法)执行结束。有2种方法。

(1) 直接手动创建标志位来区分线程是否要结束.

public class ThreadDemo5 {
    static boolean flog = true;
    public static void main(String[] args)  {
        Thread t = new Thread(()->{
           while (flog){
               try {
                   Thread.sleep(500);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
               System.out.println("线程运行中~~");
           }
            System.out.println("t 线程结束");
        });
        t.start();
        try {
            //睡眠3000ms
            Thread.sleep(3000);
            //将标志位设置为false终止上面的while()
            System.out.println("控制新线程结束");
            flog = false;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行结果:

在这里插入图片描述
有些线程可能会存在一些循环需要执行的情况,但是可能只是需要执行一定时间,并不是一直执行,所以就可以通过sleep()来控制这个线程结束的时间.

需要注意的是:flog需要是成员变量,局部变量是线程私有的,其余线程不能进行修改,但是成员变量是处于进程的数据区,是共享的资源,所以可以访问和修改~~

Thread内置了标志位,不需要手动创建标志位。

(2)Thread内置的标志位

public class ThreadDemo5 {
    static boolean flog = true;
    public static void main(String[] args)  {
        Thread t = new Thread(()->{
           while (!Thread.currentThread().isInterrupted()){
               try {
                   Thread.sleep(500);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
               System.out.println("线程运行中~~");
           }
            System.out.println("t 线程结束");
        });
        t.start();
        try {
            //睡眠3000ms
            Thread.sleep(3000);
            //将标志位设置为false终止上面的while()
            System.out.println("控制新线程结束");
           t.interrupt();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

在这里插入图片描述
Thread.currentThread().这个是Thread类的静态方法,通过这个方法可以获取当前线程,类似this;

isInterrupted()方法是一个判定内置的标志位,默认是false,true表示线程要中断。现在运行,查看结果。

在这里插入图片描述

为什么在抛出异常后线程还是在不停的执行呢?

那是因为当代码执行到interrupt()时会做两件事:

(1) 如果t 线程没有处于阻塞状态时,这时候interrupt()就会修改内置标志位,将isInterrupted的状态设置为true;

(2) 如果 t 线程处于阻塞状态时,此时interrupt()会通过触发异常,将阻塞状态(sleep)提前唤醒,但是由于把sleep唤醒会导致标志位变成false,也就是将标志位清空,如果标志位本身就是false则不变.由于我们的循环条件是while(!Thread.currentThread().isInterrupted()),又因为我们的标志位又变成了false,所以我们可以继续执行这个循环.

实际中能产生阻塞的方法有很多,并不是只有sleep,这里只是用sleep举例子.

由于计算机的执行速度时很快的,所以阻塞状态大概占线程执行状态的99.99%,所以会有更大的几率触发(2).

当然,即使是大部分时间都在阻塞状态,但是该线程始终是在阻塞状态和正常运行的状态之间切换.

当然,我们可以通过break关键字,当抛出异常时直接break就可以不再执行循环,之后就可以结束线程,这就相当于我正在打游戏,我妈喊我吃饭,此时我不敢抗拒,只能去吃饭.

代码如下:

public class ThreadDemo5 {
    public static void main(String[] args)  {
        Thread t = new Thread(()->{
           while (!Thread.currentThread().isInterrupted()){
               try {
                   Thread.sleep(500);
               } catch (InterruptedException e) {
                   e.printStackTrace();
                    break;
               }
               System.out.println("线程运行中~~");
           }
            System.out.println("t 线程结束");
        });
        t.start();
        try {
            //睡眠3000ms
            Thread.sleep(3000);
            //将标志位设置为false终止上面的while()
            System.out.println("控制新线程结束");
           t.interrupt();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

此时的运行结果:

在这里插入图片描述
当然,由于我们捕捉到了异常,我们不仅可以直接让他break,还可以让他先等待一会再结束执行.

就像我们在打电话一样:

比如我正在打游戏,女朋友让我出去买菜,此时我不搭理她,这个时候就相当于,你说你的我干我的.

但是呢?过了一会女朋友又来跟我说让我买菜去,我因为怕她生气嘛,所以我跟她说,五分钟,五分钟就好…等五分钟一到,我就退出游戏买菜去了.

当我买完菜的时候,刚躺到床上打开游戏,此时厨房传来女朋友的尖叫声,那我必须立刻放下手机去看看发生了什么事情…

总结三种状态;(1)立即退出 (2) 等待一会再退出 (3) 不退出,你说你的我做我的.

由我们自己来决定什么时候退出.

如果是直接写死的话,让我收到命令就必须退出,假如我在跟老板汇报工作,此时女朋友让我去买菜,如果我把老板电话挂掉,那自己也就寄了.

只有自己才有资格决定自己要做什么事情,别人的话之能当作意见,只有自己是为自己好.

等待一个线程

线程是一个随机调度过程,等待线程,就是控制两个线程的结束顺序.

但是呢?我们可以通过过join()方法来控制线程的执行顺序.

大家看下面的代码以及执行结果:

在这里插入图片描述
由于线程是抢占式执行的,所以可能这次是先打印 main 但是下次可能就先打印t了,可是程序员不喜欢这种不确定性,所以我们可以通过join()方法来控制顺序.

大家看下面的代码和执行结果:

在这里插入图片描述
当我们在 main 线程中调用 t.join() ,此时只有当 t 线程执行完之后,才会去执行 main 线程中的内容,我们也可以理解为,在哪个线程中调用join()方法,那么就哪个线程阻塞,只有当调用这个方法的引用所对应的线程执行完之后,才可以继续执行后续代码~

在这里插入图片描述
当然,这个join()也是可以传参数的,假如 t 线程一直没有执行完,那么我这个线程就不工作了? 就像我约女神见面一样,我等了一天,两天,三天都没等到,难道我还要等一辈子? 所以此时我们就可以传一个参数,作为最大等待时间,超过这个时间我就不等了,世界上美女那么多,我换个人舔还不行?

join方法自身也会阻塞,Java线程但凡是阻塞的方法都可能抛出这个异常(throws InterruptedException)。

join有两种行为:

(1)如果被等待的线程还没有执行结束,那么直接阻塞等待;

例如:我接我孩子放学,他5.00放学,我4.50到了,此时我就需要等待他放学,菜才能回家.

(2)如果被等待的线程已经执行结束了,就直接返回。

例如:我接我孩子放学,他5.00放学,我5.10才到,那么我就不用等,可以直接接回家.

join方法不带参数的时候,就是死等,一直等待下去。方法带参数的时候,如果超过了等待的最大时间,就开始执行下一步的代码。一般写带参数的join方法。

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

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

相关文章

Tomcat部署及优化

目录 1.Tomcat概述 1.Tomcat的概念 2、Tomcat的核心组件 3.Java Servlet 的概念 4.JSP的概念 5.Tomcat中最顶层的容器------server 6.四个子容器的作用 7.Tomcat请求过程 2.Tomcat服务部署 1.Tomcat服务部署的步骤 2.实例操作:Tomcat服务部署 3.Tomcat 虚拟主机配置…

数据清洗是清洗什么?

在搭建数据中台、数据仓库或者做数据分析之前,首要的工作重点就是做数据清洗,否则会影响到后续对数据的分析利用。那么数据清洗到底是做什么事情呢?今天我就来跟大家分享一下。 数据清洗的基本概念 按百度百科给出的解释,“数据清…

Java之链表(不带头结点,带头结点,迭代实现,递归实现)

目录 一.链表 1.什么是链表 2.链表的分类 二.不带头结点单向链表的非递归实现 1.接口的定义 2. 不带头结点单向链表的结构 3.链表的添加操作(头插法和尾插法) 1.头插法 2.尾插法 4. 链表的插入操作 5.链表的删除操作 1.删除指定索引的结点 2.删除指定值的第一个结点…

一文带你领略 WPA3-SAE 的 “安全感”

引入 WPA3-SAE也是针对四次握手的协议。 四次握手是 AP (authenticator) 和 (supplicant)进行四次信息交互,生成一个用于加密无线数据的秘钥。 这个过程发生在 WIFI 连接 的 过程。 为了更好的阐述 WPA3-SAE 的作用 …

Thread的小补丁

Thread小补丁线程状态NewRunnableWaitingTimed_waitingBlocked线程安全线程的抢占式执行同时对同一个变量进行修改指令重排序操作不是原子的解决方案万恶之源优化我们自己的代码Synchronized和Volatile上一篇博客中,我们简单介绍了线程Thread的一些知识,一些基本的使用,但是单单…

数据结构和算法(1):数组

目录概述动态数组二维数组局部性原理越界检查概述 定义 在计算机科学中,数组是由一组元素(值或变量)组成的数据结构,每个元素有至少一个索引或键来标识 In computer science, an array is a data structure consisting of a col…

文心一言发布,你怎么看?chatGPT

百度全新一代知识增强大语言模型“文心一言”于2021年3月16日正式发布,作为一款自然语言处理技术,它引起了广泛的关注和讨论。 首先,文心一言是一款具有重大意义的自然语言处理技术。在人工智能领域,自然语言处理技术一直是一个难…

PyTorch 之 神经网络 Mnist 分类任务

文章目录一、Mnist 分类任务简介二、Mnist 数据集的读取三、 Mnist 分类任务实现1. 标签和简单网络架构2. 具体代码实现四、使用 TensorDataset 和 DataLoader 简化本文参加新星计划人工智能(Pytorch)赛道:https://bbs.csdn.net/topics/613989052 一、Mnist 分类任…

Lambda表达式

第一章 Java为什么引入 Lmabda表达式目的尽可能轻量级的将代码封装为数据1.1 什么是Lambda表达式Lambda表达式也被成为箭头函数、匿名函数、闭包 Lambda表达式体现的是轻量级函数式编程思想 ‘->’符号是Lambda表达式的核心符号,符号左侧是操作参数,符…

YOLOv8 多目标跟踪

文章大纲 简介环境搭建代码样例跟踪原理代码分析原始老版实现新版本封装代码实现追踪与计数奇奇怪怪错误汇总lap 安装过程报错推理过程报错参考文献与学习路径简介 使用yolov8 做多目标跟踪 文档地址: https://docs.ultralytics.com/modes/track/https://github.com/ultralyt…

【多线程】多线程案例

✨个人主页:bit me👇 ✨当前专栏:Java EE初阶👇 ✨每日一语:we can not judge the value of a moment until it becomes a memory. 目 录🍝一. 单例模式🍤1. 饿汉模式实现🦪2. 懒汉模…

java如何创建线程

java如何创建线程1. java如何创建线程1.1 通过继承Thread类来创建线程1.2 通过实现Runnable接口来创建线程1.3 通过匿名内部类来创建线程1.4 lambda表达式1.5 通过实现Runnable接口的方式创建线程目标类的优缺点1. java如何创建线程 一个线程在Java中使用一个Thread实例来描述…

android8 rk3399 同时支持多个USB摄像头

文章目录一、前文二、CameraHal_Module.h三、CameraHal_Module.cpp四、编译&烧录Image五、App验证一、前文 Android系统默认支持2个摄像头,一个前置摄像头,一个后置摄像头需要支持数量更多的摄像头,得修改Android Hal层的代码 二、Camer…

VueX快速入门(适合后端,无脑入门!!!)

文章目录前言State和Mutations基础简化gettersMutationsActions(异步)Module总结前言 作为一个没啥前端基础(就是那种跳过js直接学vue的那种。。。)的后端选手。按照自己的思路总结了一下对VueX的理解。大佬勿喷qAq。 首先我们需要…

我的 System Verilog 学习记录(11)

引言 本文简单介绍 SystemVerilog 的其他程序结构。 前文链接: 我的 System Verilog 学习记录(1) 我的 System Verilog 学习记录(2) 我的 System Verilog 学习记录(3) 我的 System Verilo…

Linux lvm管理讲解及命令

♥️作者:小刘在C站 ♥️个人主页:小刘主页 ♥️每天分享云计算网络运维课堂笔记,努力不一定有收获,但一定会有收获加油!一起努力,共赴美好人生! ♥️夕阳下,是最美的绽放&#xff0…

软件行业的最后十年【ChatGPT】

在这篇文章中,我将说明像 ChatGPT 这样的生成式人工智能 (GAI) 将如何在十年内取代软件工程师。 预测被离散化为 5 个阶段,总体轨迹趋向于完全接管。 但首先,一个简短的前言。 推荐:用 NSDT场景设计器 快速搭建3D场景。 1、关于AI…

二叉搜索树:AVL平衡

文章目录一、 二叉搜索树1.1 概念1.2 操作1.3 代码实现二、二叉搜索树的应用K模型和KV模型三、二叉搜索树的性能分析四、AVL树4.1 AVL树的概念4.2 AVL树的实现原理4.3 旋转4.4 AVL树最终代码一、 二叉搜索树 1.1 概念 二叉搜索树( Binary Search Tree,…

LeetCode刷题记录---数位DP算法

😄 学会数位dp算法,可以连杀好几道力扣困难题,加油~ 🚀题目: 难度题目困难2376. 统计特殊整数困难1012. 至少有 1 位重复的数字困难233. 数字 1 的个数困难面试题 17.06. 2出现的次数🚀学习资料: 数位dp算法,我是跟着灵神学的,感谢灵神!数位 dp 通用模板参考灵神…

Python数据分析案例24——基于深度学习的锂电池寿命预测

本期开始案例较为硬核起来了,适合理工科的硕士,人文社科的同学可以看前面的案例。 案例背景 这篇文章是去年就发了,刊物也印刷了,现在分享一部分代码作为案例给需要的同学。 原文链接(知网文章 C核)&…
最新文章