成都汇智动力老师直接接听

400-029-09** 400-029-0997 转 65635
查看完整号码
扫码拨号
微信扫码拨号

Java开发之高并发必备篇(四)——线程的状态、调度和操作方法

作者:汇智动力学院 来源:汇智动力学院 2022/10/6 14:19:42

之前的文章中我们已经介绍了线程的创建方式,以及线程并发的现象...

之前的文章中我们已经介绍了线程的创建方式,以及线程并发的现象和原理结构,我们已经对于解决线程并发问题有了了解,但是在做线程并发安全的问题之前呢,我们先了解下Java中线程的几个状态、线程的调度以及线程的一些操作方法。 1.Java线程的状态 我们知道当我们创建了Thread对象,并调用start方法之后,我们的线程就运行起来了,但是线程运行起来之后处于一个什么样的状态,我们又如何对线程的状态进行转换呢?其实呢Java中对于线程总共设定了5个状态,分别为: 新建状态、就绪状态、运行状态、阻塞状态、死亡状态 。并且在任意一时间点一个线程只能有一个状态。线程5种状态介绍如下: 新建状态(New ) :顾名思义就是我们通过new Thread() 创建了线程,但是还并未启动线程。 就绪状态(Runnable) :当其他线程调用start 方法启动该线程的时候,线程首先会进入准备就绪状态也被称为“可运行状态”,随时等待线程调度程序获取CPU的执行时间(即CPU时间片)。 运行状态(Running) :,线程调度程序一旦获取到了CPU的执行时间线程就进入运行状态并执行线程的程序代码。 阻塞状态(Blocked) :阻塞状态是指线程因为某种原因放弃了cpu 使用权,让出了cpu的执行时间。直到线程进入“可运行状态”,才有机会再次获得cpu 执行时间 转到“运行状态”。导致线程阻塞主要有三种情况: ① 无限等待:当调用了没有时间参数的Object.wait()、Thread.join()、LockSupport.park()等方法,当前线程就会处于无限等待状态,这种等待需要其他线程显示的唤醒才能重新获取CPU执行时间进入运行状态,例如:调用Object.notify()可以唤醒调用Object.wati()阻塞的线程。 ② 限时等待:当调用了Thread.sleep()、Object.wait(timeout)、Thread.join(millis)、LockSupport.parkNanos(nanos)、LockSupport.parkUnit(deadline)等方法,当前线程也会处于等待状态,但是无须等待被其他线程显示的唤醒,在一定时间后它们会由系统自动唤醒。 ③同步等待:运行的线程在获取对象的synchronized同步锁时,若该同步锁被别的线程占用则获取失败,JVM会把该线程放入锁池(lock pool)中,它会进入同步阻塞状态。等占用同步锁的线程释放了同步锁之后,线程就会再次尝试获取同步锁,如果获取成功则进入运行状态。 死亡状态(Dead) :当线程执行代码运行完之后或者因为异常退出了run方法,那么该线程都会结束其生命周期。 下面这张图就很好的介绍了线程的5个状态以及转换过程:
2.Java线程的调度 之前提到了线程获取到CPU执行时间的时候就会进入运行状态,否则就会再可运行状态或者阻塞状态,那么系统是如何分配线程时间片以及实现线程的调度的呢?下面我们就来讲讲线程的调度策略。 线程调度是指系统为线程分配CPU执行时间片的策略方式,主要调度方式有两种: 协同式线程调度(Cooperative Threads-Scheduling) 和 抢占式线程调度(Preemptive Threads-Scheduling) 。 协同式线程调度 该调度策略模式下线程的执行时间由线程本身来控制,某一线程执行完了之后,会主动通知系统切换到另外一个线程上执行(可以想象下类似排队一样的场景)。协同式多线程的*大好处是实现简单,而且由于线程获取执行时间和切换由自己控制,切换操作对线程自己是可知的,所以没有什么线程同步的问题。但是它缺点很明显:如果一个线程编写有问题,那么线程运行了一部分之后就一直堵塞,一直不告诉系统进行线程切换,进程一直不让CPU执行时间严重时可能导致整个系统崩溃。
抢占式线程调度 该调度策略模式下每个线程的执行时间以及线程的切换都将有系统分配和控制;在这种实现线程调度的方式下,线程的执行时间是系统可控的,可能一个线程都分同样的执行时间片,也可能是某些线程执行的时间片较长,甚至某些线程得不到执行的时间片,这种调度策略下如果某个线程阻塞了也不会导致整个进程阻塞的问题,Java使用的线程调度方式就是抢占式调度。
3.线程操作的主要方法 Java中既然是抢占式线程调度模式,那么我们哪些操作方法可以让线程获取CPU的执行时间片呢?又有哪些操作可以阻塞线程呢?下面我们就来看看线程常见的一些操作方法。 (1)获取当前线程及其Name信息 Thread.currentThread();表示获取当前正在执行的线程,这样我们就可以操作当前的线程。例如获取当前线程的名称,或者设置名称:
Java程序运行的话都是执行的主线程main线程,而其他线程都是在主线程中new出来的也被称为“子线程”,上面的代码我们在main方法中执行的,所以返回的结果为主线程的名字“main”。同样也可以通过setName 方法修改线程的名称。
(2)线程的睡眠-sleep Thread.sleep(millis); sleep方法是Thread类中的静态方法所以可以直接调用,调用了sleep方法,那么当前线程会让出CPU的执行时间而进入阻塞等待状态,等待的时间一到就会再次进入就绪状态抢夺CPU的执行时间片。使用案例如下:
代码的意思就是i会每隔50毫秒输出一次。 分析: Thread.sleep()的好处在于短时间内可以让出cpu资源给其他线程运行,并且睡眠时间一到就会自动苏醒到就绪状态然后到运行状态,并且Thread.sleep()只能睡眠当前的线程。 Thread.sleep(0)的妙用: TimeUnit 线程睡眠的便利使用: 当我们希望我们的线程睡眠时间特别久的时候,如果我们使用Thread.sleep()方法发现时间计算比较麻烦,并且不直观,比如我们需要线程睡眠3个小时10分钟,如果我们使用Thread.sleep()那么就需要计算3个小时10分钟换算成毫秒值为多少,太麻烦了。怎么办?Java中的TimeUnit提供了优雅简单的调用方式,如下:
同样方法还有TimeUnit.DAYS、TimeUnit.SECONDS 等; (3)线程的优先级-Priority 线程同样也有优先级设定,线程的优先级从1到10,数字越大则优先级越高,如果我们创建线程而没有设置优先级的话,那么优先级就是默认的5;值得注意的是并不是优先级高的线程就一定会比优先级低的线程先执行,线程的优先级越高表示的是获取到的CPU执行时间片越多,跟优先级低的线程比抢夺到CPU执行时间的几率就越高。我们可以通过Thread的setPriority()方法来设置线程的优先级,同样也可以通过getPriority();方法获取线程的优先级。案例如下:
运行结果:
运行多次程序结果分析我们会发现线程2获取到的机会更大一些。 (4)线程的礼让-yeild Thread.yeild() 是Thread类的静态方法可以直接调用表示线程的礼让,线程的礼让指的是当前线程暂时放弃CPU的执行时间礼让一下给其他线程,而礼让的同时自身进入就绪状态。因为礼让之后自身也进入就绪状态,所以yeild礼让之后自身还是有几率抢夺到CPU的执行时间,只不过一旦礼让之后优先级越高的线程抢夺到的几率越高。举个例子:好比排队买菜,某一天轮到张三买菜了,但是张三礼让了一下说“大家公平竞争,谁今天先到卖菜的地方谁就先买”而张三这么一礼让明天买菜的人就看谁先到了,有可能买菜的是他也有可能是别人。 案例如下:
运行结果:
我们发现并不是每次都能礼让成功的。虽然yeild方法可以做到礼让,但是其实在开发中用的场合很少。更多是用于调式和测试的作用,源代码说明如下:
(5)线程的插队-join Java线程面试的时候很多人遇到过这样的问题,“Java中如何让多个线程按照自己指定的顺序执行?”没错,这个问题*简单的实现就是使用Thread的join()方法来实现了。 thread.join()的意思表示当前线程会从运行状态变为阻塞状态,需要等待插入的线程执行完终止之后,才会从thread.join的阻塞状态变为可运行状态。案例如下:
运行下代码我们就会发现,无论运行多少次,一直就是线程t1的代码先执行完毕然后才执行到t2的代码。 当然join()方法并不是用来保证线程的顺序性的。查看下join的源代码我们会发现其实底层还是使用的Object类的wait()方法,如图:
join方法中当通过isAlive()方法判断当前线程是运行状态的时候,就进行阻塞主线程让出cpu的资源给t1线程,而t1可以一直抢夺cpu资源到执行完成是因为join方法被 synchronized 修饰了,之前介绍过synchronized关键字的一些意思,这里我们先简单说明下被synchronized修饰的方法调用线程就会获取到同步锁,获取同步锁的线程可以一直执行完毕才会释放锁给其他线程执行。 join()方法主要用于一些多线程协调完成一个任务的执行,或者可以顺序执行的场景。 (6)线程的中断 之前我们在使用Thread.sleep()和Thread.join()的时候发现都需要抛出异常InterruptedException即线程被中断异常。为什么要抛出这个异常? 在Java中,一个线程是不能终止另一个线程的,除非线程自己程序想退出或者程序结束了。以前的时候Thread类提供了stop()、destroy()等方法可以强制结束一个线程,但是现在这些方法都没有得到保留下来。 那如何结束一个线程呢?其实每个线程都拥有一个flag,标志着线程的中断标识。如果一个线程A想让线程B退出,则A将B的中断标示(interrupt flag)置为true,我们说“线程A向线程B发了中断信号”。此时如果B检查到了中断标识为true,说明有线程想让它中断,线程B通过自己判断是否需要自愿退出(也可以不退出,不能强制)。 而在执行一些耗时操作的时候,例如sleep()、join()、wait()等,需要经常check interrupt的状态,并且一旦发现为true,就会立刻抛出InterruptedException告诉你其他线程向你发送了中断信号。Java中通过Thread的interrupt(true)来设置一个线程的中断信号为true,interrupted()和isInterrupted()方法可以获取线程的中断标识是否为true,不同的是interrupted()在获取的标识后会清除标识即把标识改为false。我们看下面结束线程的案例:
运行代码,当主线程调用t.interrupt();的时候子线程t执行结束。 (7)守护线程(后台线程) 守护线程又叫“服务线程”,它是后台线程,守护线程最显著的特点就是JVM中如果没有用户创建的前台线程的时候就会自动退出,所以守护线程一般都是给程序中其他对象和线程提供一些公共服务或者进行一些后台任务执行。 守护线程的优先级都很低,典型的守护线程就是GC(垃圾回收器),都知道GC主要负责我们JVM堆和方法区中内存的回收,如果JVM中除了GC线程外其他的都运行完没有了,那么GC也就不需要回收垃圾所以作为守护线程就自动退出了。 Java中通过setDaemon(true)来设置线程为“守护线程”,案例代码如下:

以上就是我们线程中的一些基本的操作方法使用和讲解了,通过这些方法的运用希望大家对于线程的了解和使用更加的详细,下一篇中我们讲开始讲解我们如何保证线程并发安全的问题。 二、算法的设计原则   ①、正确性:首先,算法应当满足以特定的“规则说明”方式给出的需求。其次,对算法是否“正确”的理解可以有以下四个层次:   一、程序语法错误。   二、程序对于几组输入数据能够得出满足需要的结果。   三、程序对于精心选择的、典型、苛刻切带有刁难性的几组输入数据能够得出满足要求的结果。   四、程序对于一切合法的输入数据都能得到满足要求的结果。   PS:通常以第 三 层意义的正确性作为衡量一个算法是否合格的标准。   ②、可读性:算法为了人的阅读与交流,其次才是计算机执行。因此算法应该易于人的理解;另一方面,晦涩难懂的程序易于隐藏较多的错误而难以调试。   ③、健壮性:当输入的数据非法时,算法应当恰当的做出反应或进行相应处理,而不是产生莫名其妙的输出结果。并且,处理出错的方法不应是中断程序执行,而是应当返回一个表示错误或错误性质的值,以便在更高的抽象层次上进行处理。   ④、高效率与低存储量需求:通常算法效率值得是算法执行时间;存储量是指算法执行过程中所需要的*大存储空间,两者都与问题的规模有关。   前面三点 正确性,可读性和健壮性相信都好理解。对于第四点算法的执行效率和存储量,我们知道比较算法的时候,可能会说“A算法比B算法快两倍”之类的话,但实际上这种说法没有任何意义。因为当数据项个数发生变化时,A算法和B算法的效率比例也会发生变化,比如数据项增加了50%,可能A算法比B算法快三倍,但是如果数据项减少了50%,可能A算法和B算法速度一样。所以描述算法的速度必须要和数据项的个数联系起来。也就是“大O”表示法,它是一种算法复杂度的相对表示方式,这里我简单介绍一下,后面会根据具体的算法来描述。   相对(relative):你只能比较相同的事物。你不能把一个做算数乘法的算法和排序整数列表的算法进行比较。但是,比较2个算法所做的算术操作(一个做乘法,一个做加法)将会告诉你一些有意义的东西;   表示(representation):大O(用它*简单的形式)把算法间的比较简化为了一个单一变量。这个变量的选择基于观察或假设。例如,排序算法之间的对比通常是基于比较操作(比较2个结点来决定这2个结点的相对顺序)。这里面就假设了比较操作的计算开销很大。但是,如果比较操作的计算开销不大,而交换操作的计算开销很大,又会怎么样呢?这就改变了先前的比较方式;   然后我们再说说算法的存储量,包括:    程序本身所占空间;    输入数据所占空间;    辅助变量所占空间;   一个算法的效率越高越好,而存储量是越低越好。 三、算法的分类 算法可以宏泛的分为三类: 一,有限的,确定性算法 这类算法在有限的一段时间内终止。他们可能要花很长时间来执行指定的任务,但仍将在一定的时间内终止。这类算法得出的结果常取决于输入值。 二,有限的,非确定算法 这类算法在有限的时间内终止。然而,对于一个(或一些)给定的数值,算法的结果并不是的或确定的。 三,无限的算法 是那些由于没有定义终止定义条件,或定义的条件无法由输入的数据满足而不终止运行的算法。通常,无限算法的产生是由于未能确定的定义终止条件。 Java中常见的算法有: ①、排序 排序就是对一组数据按照一定的顺序(从大到小或者从小到大)进行排序; 常见排序如下: 简单排序:冒泡排序、选择排序、插入排序; 高级排序:快速排序、希尔排序、归并排序、基数排序、鸡尾酒排序等等; ②、递归 递归是一种直接或者间接调用自身的一种算法,递归的目的是简化程序设计使程序更加易读; ③、查找 在一些(有序的/无序的)数据元素中,通过一定的方法找出与给定关键字相同的数据元素就叫做查找; ④、统计 指对有关数据的搜集、整理、计算、分析、解释、表述等的活动。 往期文章 墙裂推荐 [ 1 ] Java开发之高并发必备篇(三)——线程的内存模型 [2 ] Java开发之高并发必备篇(二)——线程为什么会不安全? [3 ] Java开发之高并发必备篇(一)——线程基础 原创视频 墙裂推荐 2022转行软件测试之开班动态 ↓↓↓

活动福利 // 1 毕业礼包 | 毕业学员免费赠送《软件测试技术大咖专题课》,助力学员早日突破高薪瓶颈 // 2 入职礼包 |就业学员免费赠送《Java语言开发视频课》及全套源代码,市场价值12800元 // 3 推荐有奖 |推荐好友成功报名,立得丰厚“伯乐”红包 (欢迎详询校区老师)

详询软件测试&开发培训事宜

添加微信咨询
杨老师 @成都汇智动力

专业解答各类课程问题、介绍师资和学校情况

微信号:186******73

立即咨询

“成都汇智动力”是成都汇智动力信息技术有限公司在教育宝平台开设的店铺,若该店铺内信息涉嫌虚假或违法,请点击这里向教育宝反馈,我们将及时进行处理。

机构评分

环境:5.0师资:5.0服务:4.0效果:4.0

公示信息

店铺名称:成都汇智动力

单位名称:成都汇智动力信息技术有限公司

账号名称:cdhzdl(180******07)

所属城市:四川成都

入驻时长:12年

在线客服:在线聊

微信咨询

返回顶部