按照顺序执行_问一个多线程的问题:如何才能保证线程有序执行?

8bebc31b8c89acaf4dc8fe7bfa45d625.png

面试的时候你是否经常被问到这样的问题:

你一般通过什么方式去控制线程的执行顺序?

碰到这样的问题,我的内心其实是很抵触的!

de36c9821d3cd83aa1e0e8c4b99abf36.png

开什么玩笑?我怎么会控制它呢?我为什么要控制它?

其实不用慌,这个问题并不难,且听我慢慢道来......

a7c169555878d87677e082203cc16c75.png

一、那么,什么是线程、进程?

要想控制多线程的顺序,你首先应该搞清楚线程和进程到底是什么东西

1、进程

进程其实是操作系统的基础,是系统中一次程序的执行,也是一次程序和数据在机器上顺序执行时所发生的活动,又是系统进行资源分配和调度的一个独立单位。

其实说的通俗一点,可以这么理解,进程就是Windows系统中执行的一个exe程序,是操作系统管理的基本运行单元,看下面这个图你就知道啥是进程了!

74d57595b3b220ba630418714a235103.png

2、线程

线程是比进程还要小的一个单元,它是进程中独立运行的子任务。

你比如说一个微信.exe程序进程中就有非常多的子线程在同时运行,例如音视频线程、文件下载线程、信息传输线程等等,这些不同的任务如果都在一个线程去运行,那程序必定会特别慢,大家的体验不会像现在那样舒服,所以这里的每一个任务或功能都需要对应一个后台的线程在默默运行。

简单来说,线程就是组成进程的一条路径,一个进程可以包含一个或者多个线程。

二、什么是多线程环境?

关于多线程的环境,其实大家在使用Windows系统的时候就深有感触。

想象你一边在用IDE码代码,一边要和朋友聊天,还一边带着耳机听着音乐,你的系统为什么能够同时提供给你那么多服务呢?

其实这就是一个典型的多线程环境,你的电脑的CPU正在不断的从这些任务中飞速切换来处理程序中的各种事情,由于计算机的切换处理速度非常的快,所以你没办法在界面上进行感知,给我们的感觉就是他们其实在同时为我们服务着,这也是多线程环境的一种优势!

用一张图的方式来对比感受一下多线程的环境,例如:

61ff0ec7b756b647866afd4aade4cabf.png

在上图中,单线程环境下一号、二号处理任务是完全独立的两个任务,但是二号任务必须要等待1号任务处理完成才能执行,也就是二号处理任务必须要在程序开始后的5秒后才能运行到,最终的运行时间为15秒。

但是在多线程环境下,一号、二号虽然也是完全独立的任务,处在同一个进程中,但却由不同的线程去处理,CPU可以在这两个不同的线程之间进行切换,所以二号处理任务不需要在一号处理完成之后再处理,而是做异步处理,最终的运行时间也差不多在10秒左右。

三、几个关于CPU、线程执行的知识点

1、在单CPU计算机中,CPU是无法被多个程序并行使用的。

2、操作系统中存在一种调度器,它可以负责拆分CPU为一段段时间的运行片,轮流分配给不同的进程。

3、程序的运行不仅仅需要CPU,还需要很多其他资源,如内存啊,显卡啊,GPS啊,磁盘等等,这些统称为程序的执行环境,也就是程序上下文。

4、多个程序没办法同一个时间共享CPU,那怎么办呢?这个时候比进程更小的线程就出来了,通过在不同线程的切换来达到共享CPU、共享程序上下文的目的。

5、大家都知道,CPU有单核和多核区别,单核CPU其实就是多个线程会轮流得到那一个CPU核心的支持;在多核CPU中,一个核心可以服务于一个线程,例如我的电脑是4核的话,有四个线程A、B、C、D需要处理,那CPU会将他们分配到核心1、2、3、4,如果还有其他更多的线程,也必须要等待CPU的切换执行。

通过上面几个知识点,可以看出不管是在多核还是单核的系统中,CPU在多线程的环境中都是要不断切换线程来处理任务的,当然,CPU在切换任务时也不是顺便切换,而是根据一定的算法来调度、切换线程,一般有这样两种模式:分时调度和抢占式调度

分时调度就是按照顺序平均分配;

抢占式调度就是按照优先级来进行分配。

具体的算法逻辑在这里就不再详细描述,有疑问的小伙伴们可以再继续往下深入探索......

四、如何去控制多线程的执行顺序?

通过一个简单的程序,体验一下线程是否会随机被执行,手动创建5个Thread对象,然后让他们按照顺序开启,如下面代码:

/*** 多线程Test*/
public class Main {static Thread thread1 = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("thread01");}});static Thread thread2 = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("thread02");}});static Thread thread3 = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("thread03");}});static Thread thread4 = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("thread04");}});static Thread thread5 = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("thread05");}});public static void main(String[] args) {thread1.start();thread2.start();thread3.start();thread4.start();thread5.start();}
}

最后执行的结果大家一定已经猜到了,多次执行,它的顺序并不是固定的,而是随机在改变的,例如:

e142de514c4ecf5ac5fbbafe9bae86d1.png

那么在多线程的环境中,我们有时候不想让CPU根据算法随机选取任务执行,而是想控制多线程的执行顺序,那应该如何操作呢?

目前我知道的主要有两种方法:

1、Join方法

我们直接通过在每个Thread对象后面使用join方法就可以实现线程的顺序执行,代码如下:

 public static void main(String[] args) throws Exception {thread1.start();thread1.join();thread2.start();thread2.join();thread3.start();thread3.join();thread4.start();thread4.join();thread5.start();thread5.join();}

多次执行结果都为下面这种情况:

140dc95ee551522571b69197e6c30663.png

用join方法来保证线程顺序,其实就是让main这个主线程等待子线程结束,然后主线程再执行接下来的其他线程任务,点进去join方法我们可以了解的更透彻:

 /*** Waits at most {@code millis} milliseconds for this thread to* die. A timeout of {@code 0} means to wait forever.*/public final synchronized void join(long millis)throws InterruptedException {long base = System.currentTimeMillis();long now = 0;if (millis < 0) {throw new IllegalArgumentException("timeout value is negative");}if (millis == 0) {while (isAlive()) {wait(0);}} else {while (isAlive()) {long delay = millis - now;if (delay <= 0) {break;}wait(delay);now = System.currentTimeMillis() - base;}}}

源码中的参数millis默认值是0,从英文注释翻译后可以找到,0秒意味着永远等待,也就是thread1执行不完,那主线程你就要一直等着,一直wait,而代码中wait方法其实就是属于Object的方法,负责线程的休眠等待,当main主线程调用thread1.join的时候,main主线程会获得线程对象thread1的锁(wait 意味着拿到该对象的锁),调用该对象的wait(等待时间),直到该对象唤醒main主线程 ,比如退出后。这就意味着main 线程调用thread1.join时,必须能够拿到线程t对象的锁。

2、ExecutorService方式

首先看一下代码,我们如何通过这种方式实现线程顺序执行:

static ExecutorService executorService = Executors.newSingleThreadScheduledExecutor();public static void main(String[] args) throws Exception {executorService.submit(thread1);executorService.submit(thread2);executorService.submit(thread3);executorService.submit(thread4);executorService.submit(thread5);executorService.shutdown();}

最终的多次执行结果均为有序的,如下图:

140dc95ee551522571b69197e6c30663.png

解释一下,这种方式的原理其实就是将线程用排队的方式扔进一个线程池里,让所有的任务以单线程的模式,按照FIFO先进先出、LIFO后进先出、优先级等特定顺序执行,但是这种方式也是存在缺点的,就是当一个线程被阻塞时,其它的线程都会受到影响被阻塞,不过依然都会按照自身调度来执行,只是会存在阻塞延迟。

五、总结

总之,如果面试官真的问到大家如何控制多线程执行顺序的方法,就按照上面的两种方式回答即可,当然,面试官既然问到这个问题,就并不只是看大家是否知道这一个问题的具体答案,可能会刨根问底的让你回答更深入的一些多线程问题,所以在日常的学习过程中一定要重在积累,勤于探索,上面提到的也只是我研究到的皮毛。

最后真心希望,在以后的技术之路跟大家一起成长!

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

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

相关文章

经典sql语句50题_SQL面试经典50题:带你从建表开始

大家好&#xff0c;相信很多学习数据分析的小伙伴在面试前都经历过刷题&#xff0c;本系列小编将带大家一起来刷一刷SQL面试必会的经典50题。当然本系列文章不单单是刷题&#xff0c;小编会带着大家梳理一下解题时用到的知识点&#xff0c;所以基础比较差的小伙伴也完全不必担心…

mongodb创建local库用户_mongodb用户与角色使用

此文档以mongodb 4.0版本进行对用户权限和角色讲解,更详细内容可参考mongodb官方文档.官方文档:https://docs.mongodb.com/manual/core/security-users/一.mongodb内部角色1.数据库用户角色read --读取数据库对像的权限readWrite --读取和修改数据库对像权限2.数据库管理…

exif linux php扩展_LNMP环境为PHP添加exif扩展

在使用时 php报出如下错误: Call to undefined function gemvary\exif_imagetype()好吧&#xff0c;exif扩展没安装...通过 打印phpinfo() 查看得知 mbstring 扩展是有的&#xff0c;exif扩展没有如果mbstring也没有&#xff0c;也可以参考以下exif扩展的安装过程;安装过程基本…

宇视硬盘录像机onvif_视频监控系统中强大的录像机,兼容不同品牌,看看有哪些监控厂家...

选录像机&#xff0c;除路数、盘位和最大接入像素这些硬规格&#xff0c;大家还关注兼容性&#xff0c;例如好不好用、配置方不方便、使用流不流畅、录像稳不稳定等。偶尔也会有朋友问“TP的录像机可以搭配xx厂商的摄像机使用吗&#xff1f;”&#xff0c;答案当然是&#xff1…

python设置函数_在Python中设置函数签名

假设我有一个泛型函数f&#xff0c;我想以编程方式创建一个行为与f相同但具有自定义签名的函数f2。更多细节给定列表l和字典d&#xff0c;我希望能够&#xff1a;将f2的非关键字参数设置为l中的字符串将f2的关键字参数设置为d中的键&#xff0c;将默认值设置为d中的值假设我们有…

mysql新建数据表_Mysql创建数据库与表,并添加表的数据

首先&#xff0c;在成功安装Mysql的基础上&#xff0c;进入到cmd窗口&#xff0c;登录我们的mysql管理系统。登录方式&#xff1a;mysql -u用户名 -p密码如果出现Welcome to...等一大堆英文指示&#xff0c;则说明成功进入了mysql系统。因为我们不知道系统中有哪些库&#xff0…

mysql数据库管理维护_(转)Mysql数据库管理 表的维护

原文&#xff1a;http://t.dbdao.com/archives/mysql%E6%95%B0%E6%8D%AE%E5%BA%93%E7%AE%A1%E7%90%86-%E8%A1%A8%E7%9A%84%E7%BB%B4%E6%8A%A4.html一、目标完成本课程后&#xff0c;你应该能够&#xff1a;认识不同类型的表维护操作执行维护表的SQL语句使用客户端和实用程序维护…

mysql如何使用事件_MySQL事件的使用详解

在系统管理或者数据库管理中&#xff0c;经常要周期性的执行某一个命令或者SQL语句。这个时候就会用到mysql事件&#xff0c;使用这个功能必须保证是mysql的版本是5.1以上。1.首先要查看事件是否开启了&#xff0c;SHOW VARIABLES LIKE event_scheduler;SELECT event_scheduler…

mysql 提交乱码问题_mysql数据库乱码问题

变量 a 的字符编码是由参数 CHARACTER_SET_CLIENT 决定的&#xff0c;假设此时编码为 A&#xff0c;也就是变量 a 的编码。2. 写入语句在发送到 MySQL 服务端之前的编码由 CHARACTER_SET_CONNECTION 决定&#xff0c;假设此时编码为 B。3. 经过 MySQL 一系列词法&#xff0c;语…

mysql5.5编译安装脚本_mysql5.5 免编译安装及脚本启动报错深入

Mysql安装环境简介&#xff1a;最近在做MHA。已经安装完毕heartbeat和drbd&#xff0c;现在准备安装Mysql。Mysql安装目录&#xff1a;/opt/mysqlMysql数据目录&#xff1a;/data/mysql备注&#xff1a;/data目录实际是drbd需要同步到备节点的磁盘分区[rootmysql1 src]# df -hF…

mysql 主键索引如何创建_SQL创建索引、主键

-- MySQL中四种方式给字段添加索引1)# 添加主键# ALTER TABLE tbl_name ADD PRIMARY KEY (col_list);# -- // 该语句添加一个主键&#xff0c;这意味着索引值必须是唯一的&#xff0c;且不能为NULL。#添加唯一索引 -- UNIQUE 后面不用跟INDEX# ALTER TABLE tbl_name ADD UNIQU…

mysql查询20-30的数据_20. mysql查询表的数据大小

在需要备份数据库里面的数据时&#xff0c;我们需要知道数据库占用了多少磁盘大小&#xff0c;可以通过一些sql语句查询到整个数据库的容量&#xff0c;也可以单独查看表所占容量。1、要查询表所占的容量&#xff0c;就是把表的数据和索引加起来就可以了select sum(DATA_LENGTH…

requests由于系统缓冲区空间不足_系统C盘满了空间不足的扩容?

今天带来的是无需重新分区&#xff0c;无需删除逻辑分区的C盘容量扩大方法不重新或删除分区这就意味着无需重新安装软件、游戏等程序&#xff0c;也不用备份重要资料等操作&#xff0c;不影响电脑其它盘。统C盘满了是十分常见的问题&#xff0c;由于起初对C盘分区分的太小了&am…

mysql登陆三小时平均值图片_Mysql每小时平均值,间隔从半小时开始

我有一个mysql数据库&#xff0c;其中包含5分钟箱中的数据 . 我想从半小时开始创建每小时平均数据 . 通过使用mysql内置组&#xff1a;select date,AVG(AE) from mytable group by date(date),HOUR(date);会计算从01:00到02:00的平均值 . 相反&#xff0c;我希望从00:30到01:30…

mysql ip to int_ip网段转换程序(把ip地址转换成相对就的整数)

mysql教程_connect(localhost,root,root);mysql_select_db(dfd);$array file("ip.txt");foreach( $array as $k ){list ($ip,$s) explode(/,$k);$ipe str_replace(.0,.255,$ip);$ipint iptoint($ip);$ipinte iptoint($ipe);//echo iptoint($ip) .-.iptoint($ip…

postgre管理员 无法访问表_postgresql – 授予用户对所有表的访问权限

首先,您必须能够连接到数据库才能运行查询.这可以通过以下方式实现REVOKE CONNECT ON DATABASE your_database FROM PUBLIC;GRANT CONNECTON DATABASE database_nameTO user_name;REVOKE是必要的becauseThe key word PUBLIC indicates that the privileges are to be granted t…

mysql查询去年本月的数据_MySQL查询本周、上周、本月、上个月份数据的sql代码...

MySQL查询的方式很多&#xff0c;下面为您介绍的MySQL查询实现的是查询本周、上周、本月、上个月份的数据&#xff0c;如果您对MySQL查询方面感兴趣的话&#xff0c;不妨一看..查询当前这周的数据SELECT name,submittime FROM enterprise WHERE YEARWEEK(date_format(submittim…

python xlwings api_2021-01-13python,xlwings,api运用,及一些问题

import xlwings as xwimport re#启动APP&#xff0c;visible:是否可见&#xff0c;addbook&#xff1a;是否新增工作簿appxw.App(visibleFalse,add_bookFalse)#APP是否可见app.display_alertsFalse#是否刷新APP屏幕app.screen_updatingFalse#文件路径filepathrC:\Users\*******…

python3+requests+unittest_python3+requests+unittest:接口自动化测试(一)

简单介绍框架的实现逻辑&#xff0c;参考代码的git地址&#xff1a;1.环境准备python3 pycharm编辑器2.框架目录展示(该套代码只是简单入门&#xff0c;有兴趣的可以不断后期完善)(1)run.py主运行文件&#xff0c;运行之后可以生成相应的测试报告&#xff0c;并以邮件形式发送…

python用筛选法求解小于n的所有素数_用筛选法求解n以内的所有素数

用筛选法求解n以内的所有素数&#xff1a;筛选法的思想是一个数是素数则这个数的所有的倍数都是合数&#xff0c;我们不去找素数而去找合数&#xff0c;剩下的就是素数了。一个合数其最大的质因子不会超过其开发数,所以只要迭代到其最大数的开方数即可 一个简单的筛素数的过程&…