android 的Thread类

news2024/9/21 16:44:41

Thread类

位于java.lang包下的Thread类是非常重要的线程类,它实现了Runnable接口,学习Thread类包括这些相关知识:线程的几种状态、上下文切换,Thread类中的方法的具体使用。
线程:比进程更小的执行单元,每个进程可能有多条线程,线程需要放在一个进程中才能执行,线程由程序负责管理,而进程则由系统进行调度!
多线程的理解:并行执行多个条指令,将CPU时间片按照调度算法分配给各个线程,实际上是分时执行的,只是这个切换的时间很短,用户感觉到"同时"而已!

线程的状态

线程从创建到最终的消亡,要经历若干个状态,一般来说包括以下几个状态:

  • 创建(new)
  • 就绪(runnable)
  • 运行(running)
  • 阻塞(blocked)、主动睡眠(time waiting)、等待唤醒(waiting)
  • 消亡(dead)
    当需要新起一个线程来执行某个子任务时,就创建了一个线程。但是线程创建之后,不会立即进入就绪状态,因为线程的运行需要一些条件(比如内存资源,譬如程序计数器、Java栈、本地方法栈都是线程私有的,所以需要为线程分配一定的内存空间),只有线程运行需要的所有条件满足了,才进入就绪状态。

当线程进入就绪状态后,不代表立刻就能获取CPU执行时间,也许此时CPU正在执行其他的事情,因此它要等待。当得到CPU执行时间之后,线程便真正进入运行状态。

线程在运行状态过程中,可能有多个原因导致当前线程不继续运行下去,比如用户主动让线程睡眠(睡眠一定的时间之后再重新执行)、用户主动让线程等待,或者被同步块给阻塞,此时就对应着多个状态:time waiting(睡眠或等待一定的事件)、waiting(等待被唤醒)、blocked(阻塞)。

当由于突然中断或者子任务执行完毕,线程就会被消亡。
在这里插入图片描述

原文链接

创建线程的三种方式

  1. 通过继承Thread类本身
class MyThread extends Thread {
    @Override
    public void run() {
         . . .
    }
}
     
//启动线程    
MyThread myThread = new MyThread ();
new MyThread().start();

  1. 实现Runnalbe接口
    实现Runnalbe接口,重载Runnalbe接口中的run()方法。然后可以分配该类的实例,在创建 Thread 时作为一个参数来传递并启动。
class runnable implements Runnable {
    @Override
	public void run() {
	     . . .
	}
}
     
//启动线程  
MyRunnable runnable = new MyRunnable();
new Thread(runnable).start();

  1. 使用匿名方法类:
new Thread(new Runnable() {
    @Override
    public void run() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        tvThreadResult.setText("线程执行结果");
    }
}).start();

原文链接

上下文切换

对于单核CPU来说(对于多核CPU,此处就理解为一个核),CUP在某一个时刻只能运行一个线程,当在运行一个线程的过程中去运行另外一个线程,这个就叫做线程上下文切换。

由于可能当前的线程并没有执行完,所以在切换时需要保存线程的运行状态,以便下次线程切换回来的时候能够以上次状态去继续运行,举个简单的列子,比如一个线程A正在读取某个文件的内容,读取到一半的时候,此时CPU需要切换线程去执行线程B,当再次切换回来执行A的时候,我们不希望线程A从头开始读取,因此需要记录线程A的运行状态,下次线程回复的时候,我们需要知道线程执行到第几条指令了,搜易需要记录程序计数器的值,另外比如说线程正在进行某个计算的时候被挂起了,那么下次继续执行的时候需要知道之前挂起时变量的值时多少,因此需要记录CPU寄存器的状态。所以一般来说,线程上下文切换过程中会记录程序计数器、CPU寄存器状态等数据。

对于线程的上下文切换其实就是存储和回复CPU状态的过程,他使得线程能从断点处恢复执行。

虽然多线程可以使得任务执行的效率得到提升,但是由于在线程切换时同样会带来一定的开销代价,并且多个线程会导致系统资源占用的增加,所以在进行多线程编程时要注意这些因素。

Thread类中的方法

Thread类实现了Runnable接口,在Thread类中,有一些比较关键的属性,比如name是表示Thread的名字,可以通过Thread类的构造器中的参数来指定线程名字,priority表示线程的优先级(最大值为10,最小值为1,默认值为5),daemon表示线程是否是守护线程,target表示要执行的任务。

下面是Thread类中常用的方法:

  1. start()
    start()用来启动一个线程,当调用start()方法后,系统才会开启一个新的线程来执行用户定义的子任务,在这个过程中,会为相应的线程分配需要的资源。

  2. run()
    run()方法是不需要用户来调用的,当通过start()方法启动一个线程之后,当线程获得了CPU执行时间,便进入run方法体去执行具体的任务。注意,继承Thread类必须重写run()方法,在run()方法中定义具体要执行的任务。

  3. sleep()
    sleep方法有两个重载版本:

  • sleep(long millis) //参数为毫秒
  • sleep(long millis,int nanoseconds) //第一参数为毫秒,第二个参数为纳秒

sleep方法相当于让当前线程睡眠,交出CPU,让CPU去执行其他的任务

当前线程调用sleep()方法进入阻塞状态后,在其睡眠期间,该线程不会获得执行机会,即是系统中没有其他可执行线程,因此sleep方法常用来暂停程序执行。

但是有一点需要注意,sleep()方法不会释放锁,也就是说如果当前线程持有某个对象的锁,调用sleep()方法,其他线程就无法访问这个对象。

interrupt()

interrupt()方法解释为中断线程,实际是为了对线程做一个中断标记,但是线程还是可能还是会执行,不立即,不强制,默认不终止。

interrput()方法是替换stop()方法,stop()方法已经弃用,为什么弃用呢?
是这样,线程是一点一点执行,任何时间都有可能发生线程切换,任何时间都可以调用stop()方法,这个线程就会立即停止,可以产生非常随机的中间状态,比如在某个时间切到别的线程再也切不回来了,比如正在改某一个对象时线程停止了,会造成不可预估的影响。

所以我们要使用interrupt()方法,让程序去判断在什么时候中断当前线程,这样就能保证代码的健壮性和程序的可控性。

既然interrupt()不能立即停止线程,那么怎么才能让线程按照我们的要求停止呢?
这里我要介绍俩个方法:

  • isInterrupted()
  • Thread.interrupted()

用法:

//用于判断当前线程是否为中断状态,不会重置状态
if(isInterrupted()){
     //做一些收尾工作
     return ;
}

//用于判断当前线程是否为中断状态,先调用isInterrupted(boolean ClearInterrupted)方法,然后重置状态,true变为false,false还是false
if(Thread.interrupted()){
     //做一些收尾工作
     return ;
}

而且interrupt()可以打断睡眠状态,立即抛出异常。

	  //判断是否中断线程
  if(Thread.interrupted()){  //检查当前的线程,
     //收尾工作
  }
  try {
    Thread.sleep(2000);
  } catch (InterruptedException e) {
     //收尾工作
  }

yield()

yield()方法和sleep()方法有点相似,它也是Thread类提供的一个静态方法,它也可以让当前正在执行的线程暂停,但它不会阻塞该线程,它只是将该线程转入到就绪状态。即让当前线程暂停一下,让系统的线程调度器重新调度一次,完全可能的情况是:当某个线程调用了yield()方法暂停之后,线程调度器又将其调度出来重新执行。

调用yield方法会让当前线程交出CPU权限,让CPU去执行其他的线程。它跟sleep方法类似,同样不会释放锁。但是yield不能控制具体的交出CPU的时间,另外,当某个线程调用了yield()方法之后,只有优先级与当前线程相同或者比当前线程更高的处于就绪状态的线程才会获得执行机会。

注意,调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点是和sleep方法不一样的。

join()

join方法有三个重载版本:

  • join()
  • join(long millis) //参数为毫秒
  • join(long millis,int nanoseconds) //第一参数为毫秒,第二参数为纳秒
    假如在main线程中,调用thread.join()方法,则main()方法会等待thread线程执行完毕或者等待一定的时间。如果调用的是无参join()方法,则等待thread执行完毕,如果调用的是指定了时间参数的join方法,则等待一定的事件。
    代码示例:
public class ThreadDemo {

    private int i = 0 ;

    public static void main(String[] args) {
        ThreadDemo threadDemo = new ThreadDemo() ;

        System.out.println("进入线程"+Thread.currentThread().getName());


        MyThread thread1 = threadDemo.new MyThread() ;
        thread1.start();
        System.out.println("线程等待"+Thread.currentThread().getName());
        try {
            thread1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程继续执行"+Thread.currentThread().getName());


    }

    class MyThread extends Thread{

        @Override
        public void run() {
            synchronized (ThreadDemo.class){
                i ++ ;
                System.out.println("线程:" + Thread.currentThread().getName() + " i = " + i);
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程:" + Thread.currentThread().getName() + "--睡醒 ");

            }
        }
    }

}

获取线程属性的几个方法

  • getId() 得到线程的ID
  • getName和setName 用来得到或者设置线程名称。
  • getPriority和setPriority 用来获取和设置线程优先级。
  • setDaemon和isDaemon 用来设置线程是否成为守护线程和判断线程是否是守护线程。
  • 守护线程和用户线程的区别在于:守护线程依赖于创建它的线程,而用户线程不依赖,举个简单的例子,如果在main()线程中创建一个守护线程,当main()方法执行结束之后,守护线程也会随之消亡。而用户线程不会,用户线程会一直运行直到运行完毕,在JVM中,像垃圾收集器线程就是守护线程。
  • currentThread() 用来获取当前的线程
    8.wait()、notify()、notifyAll()
    wait()、notify()、notifyAll()这三个方法不是Thread类中的方法,是Object本地的final方法,但是多线程中也是不可或缺的。

wait()、notify()、notifyAll()和synchronized是配合使用的。

wait()在synchronized中在对应monitor维护等待队列,会把当前的锁让开,其他线程也可以访问同一个synchronized里面的代码。

notify()会唤醒同一个monitor的wait(),让monitor去唤醒,notify()唤醒wait()不确定是哪一个,所以一般不适用notify()这个方法。

notifyAll() 是唤醒同一个moitor所有的wait(),被唤醒后,需要到monitor的执行队列中等待,等待拿锁,拿锁后从wait()位置继续执行。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

0基础学习VR全景平台篇 第86篇:智慧眼-为什么要设置分组选择?

一、功能说明 分组选择,也就是给全景的每个分组去设置其所属的行政区划,设置后只有属于同行政区划的成员才可进入其场景进行相关操作,更便于实现城市的精细化管理。 二、后台编辑界面 分组名称:场景的分组名称。 对应分类&…

初识微服务

我们在曾经最常见的就是所谓的单体架构,但是由于网民越来越多,单体架构已经逐渐的被淘汰出去,所以我们在单体架构的基础上提出了微服务,它提倡将单一应用程序划分成一组小的服务,服务之间互相协调、互相配合&#xff0…

汽车级36V、4A同步降压转换器MAX20404AFOD/VY、MAX20404AFOC/VY、MAX20404AFOA/VY开关稳压器

MAX20404是小型同步降压转换器,集成了高端和低端开关。这些IC均设计为可在3V到36V的宽输入电压范围内提供高达4A的电流。电压质量可以通过观察PGOOD信号来监测。该器件可以在99%的占空比下运行,非常适合汽车和工业应用。 MAX20404提供可编程输出电压或5…

pytorch_lightning报错 You requested gpu: [1],But your machine only has: [0]

pytorch_lightning报错 You requested gpu: [1],But your machine only has: [0] 问题及分析 报错图片如下: 分析 gpu:[1]指代的gpu的标号,如果笔记本中只包含一个GPU,一般序号为[0].所以无法找到程序指定的GPU。 解决方法 …

云原生 envoy xDS 动态配置 java控制平面开发 支持restful grpc实现 EDS 动态endpoint配置

envoy xDS 动态配置 java控制平面开发 支持restful grpc 动态endpoint配置 大纲 基础概念Envoy 动态配置API配置方式动静结合的配置方式纯动态配置方式实战 基础概念 Envoy 的强大功能之一是支持动态配置,当使用动态配置时,我们不需要重新启动 Envoy…

一文教你如何实现低代码轮播图中点击图片跳转不同的H5页面

【关键字】 低代码开发、API6、轮播图组件、Item实例数据获取、H5页面跳转 1、写在前面 实际开发中我们经常会遇到这样的场景,首页轮播图加载了几张活动图片,每张图片点击之后会跳转到各自不同的活动详情页面,活动详情是通过H5页面实现的&a…

Handler机制(一)

Handler基础 Handler机制是什么? Handler是用来处理线程间通信的一套机制。 初级使用 第一步:在主线程中定义Handler private Handler mHandler new Handler(Looper.myLooper()){Overridepublic void handleMessage(NonNull Message msg) {super.ha…

CSS中的transform属性有哪些值?并分别描述它们的作用。

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ translate()⭐ rotate()⭐ scale()⭐ skew()⭐ matrix()⭐ scaleX() 和 scaleY()⭐ rotateX()、rotateY() 和 rotateZ()⭐ translateX() 和 translateY()⭐ skewX() 和 skewY()⭐ perspective()⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&…

基于react-native-date-picker的日期选择组件

基于react-native-date-picker的日期选择组件 效果示例图安装依赖封装组件PickerDateComponent使用组件device.js 效果示例图 安装依赖 https://www.npmjs.com/package/react-native-date-picker?activeTabreadme npm install react-native-date-picker --save封装组件Picker…

Jmeter 二次开发 函数助手 AES加解密

Jmeter 二次开发 函数助手 AES加解密 1. 环境准备2. 关键技术说明2.1 离线导包2.2 示例代码 3. 代码包4. 结果演示 1. 环境准备 IDE :IntelliJ IDEA 2021.1.1 x64JAVA环境 :jdk1.8.0_251离线导包:导入Jmeter安装目录下lib/ext下的ApacheJmet…

Datawhale Django后端开发入门 TASK02 Admin管理员、外键的使用

1.Admin管理员的使用 先放一张成功的截图,记得自己创建时的账号和密码呀,如果忘了的话可以也是再重新创建管理员账号和密码的 ,这个页面接下来就不用操作了,就要开始重要的 post 步骤。 二、外键的使用 我认为比较难的(很不好操作…

可视化绘图技巧100篇进阶篇(九)-三维百分比堆积条形图(3D Stacked Percentage Bar Chart)

目录 前言 适用场景 绘图工具及代码实现 帆软 实现思路 方案一:使用计算指标 上传数据 添加组件 生成图表 添加计算字段 生成分区柱形图 生成百分比堆积条形图 美化图表 设置标签 设置颜色 效果查看 PC 端 移动端 方案二:使用自助数…

运动健身耳机什么的好、健身房运动耳机推荐

对于坚持长期运动的健身人士来说,除了健身器材之外,运动耳机是必备的装备之一。因为尽管运动对身体健康有益,但过程往往感到枯燥和无聊。然而,只要有音乐作伴,情况就会好上许多。那么,什么样的耳机更适合运…

Numpy浅学

Numpy01 不要使用rank为1的,在python中(5,)既不是行向量也不是列向量,they are rank 1 arrays 行向量和列向量一定有两个括号 向量外积: 使用assert和.shape帮助Debug 可以用reshape消除rank1array

[Go版]算法通关村第十一关青铜——理解位运算的规则

目录 数字在计算机中的表示:机器数、真值对机器数进一步细化:原码、反码、补码为何会有原码、反码和补码为何计算机中的按位运算使用的是补码?位运算规则与、或、异或和取反移位运算移位运算与乘除法的关系位运算常用技巧⭐️ 操作某个位的数…

opencv进阶06-基于K邻近算法识别手写数字示例

opencv 中 K 近邻模块的基本使用说明及示例 在 OpenCV 中,不需要自己编写复杂的函数实现 K 近邻算法,直接调用其自带的模块函数即可。本节通过一个简单的例子介绍如何使用 OpenCV 自带的 K 近邻模块。 本例中有两组位于不同位置的用于训练的数据集&…

php base64转图片保存本地

调用函数 public function base64(){$img $this->request->param(img);$img data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAkACQAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIy…

详细介绍如何使用 Keras 构建生成对抗网络的源码实现

本文将演示如何使用 Keras 库构建生成对抗网络。使用的数据集是预加载到 Keras 中的CIFAR10 图像数据集。 第1步:导入所需的库 import numpy as npimport matplotlib.pyplot as plt import keras from keras.layers import Input, Dense, Reshape, Flatten, Dropout from kera…

深度学习从入门到实际项目资料汇总

图片来源于AiLake,如若侵权,请联系博主删除 文章目录 1. 介绍2. 深度学习相关学习资料2.1 [《动手学深度学习》](http://zh.d2l.ai/index.html)2.2 [导航文库](https://docs.apachecn.org/#1be32667e7914f03afb3c39239bd2525)2.3 [AI学习地图&#xff0c…

LeetCode算法心得——限制条件下元素之间的最小绝对差(TreeSet)

大家好,我是晴天学长,今天用到了Java一个非常实用的类TreeSet,能解决一些看起来棘手的问题。 1 )限制条件下元素之间的最小绝对差 2) .算法思路 初始化变量:n为列表nums的大小。 min为整型最大值,用于记录…