浅析Java的线程调度策略_java线程调度源码-程序员宅基地

技术标签: java8/9/11  jvm虚拟机  操作系统/linux  java并发编程  java·未分类  

原文链接:https://www.jianshu.com/p/3f6b26ee51ce

作者:杨兴强
原文来源:开点工作室(ID:kaidiancs)

一.从一个例子开始

看着Java线程执行起来的那种任性和随意,我们不免会问:是谁在主导Java线程的执行?它按照什么样的策略来调度Java线程?本文将带着这样的问题,探讨Java线程的调度机制。

程序的问题还是先从代码说起吧,下面是一个广泛引用的例子:
假设某航班有100张余票,现有10个窗口(线程)同时卖这100张票。下面程序实现了10个线程并发执行的过程。
// sched 类
// 创建多线程模拟多窗口售票,演示java线程的并发运行状况,说明java和操作系统的线程调度策略

class sched implements Runnable {
finalintTICKET_MAXNUM = 500;
// 总票数
finalintTHREAD_NUM = 10;
// 线程(售票窗口)数量
publicintcount = 0;
// 已售出票数
privateintThreadNo[];
// 第i张票的线程的序号是TicketThreadNo[i],-1表示未售出
privateintTicketNum[];
// 记录每个线程售出的票数,说明每个线程占用的CPU时间
privateintThreadId[];
// ThreadId[i]存放第i个线程的Id号
privateintNewThreadNo;
// 将要创建的线程的序号,初值为0
double d;
// 工作变量,仅用于消耗线程的CPU时间

 public sched() {
      inti;
      ThreadNo = newint[TICKET_MAXNUM];
      for (i = 0; i < TICKET_MAXNUM; i++)
           ThreadNo[i] = -1;
      TicketNum = newint[THREAD_NUM];
      for (i = 0; i < THREAD_NUM; i++)
           TicketNum[i] = 0;
      NewThreadNo = 0;
      ThreadId = newint[THREAD_NUM];
 }

// sell()方法描述一次售票操作,synchronized 用于实现对共享变量count的互斥
 publicsynchronizedvoid sell() {
      if (count < TICKET_MAXNUM) {
           ThreadNo[count] = getNo((int)Thread.currentThread().getId());

//System.out.println(sold[count]+ "号线程售第" + count + "张票");
count++;
//delay();
}
}
// 从线程的id号得到线程的序号
privateint getNo(intid) {
int i;
for (i=0; i<THREAD_NUM; i++)
if (ThreadId[i] == id)
returni;
return -1;
}

 publicvoid run() {

//Thread.currentThread().setPriority(NewThreadNo+1);
ThreadId[NewThreadNo++] = (int)Thread.currentThread().getId();
while (count < TICKET_MAXNUM) // 只要有票,线程(窗口)就不停售票
sell();
}
// 仅用于消耗CPU时间,表示售票所用时间
privatevoid delay(){
d = 5000000;
while (d > 0)
d = d - 1;
}
// 累计并打印每个线程卖的票数
publicvoid accumulate() {
int i;
for (i=0; i<TICKET_MAXNUM; i++)
TicketNum[ThreadNo[i]]++;
for (i=0; i<THREAD_NUM; i++)
System.out.printf("%3d号线程卖:%-4d张票\n", i, TicketNum[i]);
}
}
// 主程序
publicclass jsched {
publicstaticvoid main(String[] args) {
inti;
sched t = new sched();
for (i = 0; i < t.THREAD_NUM; i++) {
new Thread(t).start();
}
while (t.count < t.TICKET_MAXNUM) //等待票都卖完
Thread.yield();
t.accumulate();
}
}
上面例子中,主线程依次创建并启动10个线程,他们执行相同的程序run(),其作用就是不停地调用sell()以售票。NewThreadNo表示新创建线程的序号,初值为0,每创建一个新线程,加1。售票时记录每张票都是由哪个线程售出的,线程序号存于ThreadNo[]中。线程Id是每个线程在Java中的唯一标识,可以通过调用Thread.currentThread().getId()获得当前线程的Id,并由此知道正在执行当前程序的线程是哪一个线程。序号为i的线程的Id存在ThreadId[i]中,该数组记录了线程序号和Id号之间的对应关系。主线程最后统计各线程分别卖了多少张票。

在并发环境下,线程之间的运行次序总是呈现某种随机性,程序的运行结果往往是不固定的。所以我们观察程序运行结果时,需要多次运行,才能总结出大概的运行规律。当然有时也许根本就没有规律。

多次运行以上程序,较典型的结果如下:
0号线程卖:100 张票
1号线程卖:0 张票
2号线程卖:0 张票
3号线程卖:0 张票
4号线程卖:0 张票
5号线程卖:0 张票
6号线程卖:0 张票
7号线程卖:0 张票
8号线程卖:0 张票
9号线程卖:0 张票

票都被线程0卖了,其他线程好像没干活,系统采用的像是先来先服务的调度原则。尽管10个线程是并发执行的,但主线程是依次创建各个线程的。当创建了线程0之后,主线程就和线程0并发执行了。所以线程0一般情况下都先于其他线程执行。但这不能说明系统采用的是先来先服务策略。由于卖票这活儿太简单,还没等其他线程开始执行,线程0就把票卖光了。这好办,只要在sell()方法中加上延迟,把delay()函数调用前面的注释去掉,即可人为增加卖票的工作量。修改后多次运行程序,较典型的结果如下:

0号线程卖:9 张票
1号线程卖:8 张票
2号线程卖:18 张票
3号线程卖:17 张票
4号线程卖:17 张票
5号线程卖:7 张票
6号线程卖:7 张票
7号线程卖:5 张票
8号线程卖:5 张票
9号线程卖:7 张票

此时各线程都参与了卖票,而且能看到他们交替执行。系统采用的又像是时间片轮转的调度策略。

然而Java的Thread类提供了设置线程优先级的方法,每个线程初始创建时会被赋予一个默认的优先级。那么线程的优先级又起到了什么作用呢?

现在我们去掉run()方法中对设置优先级的方法setPriority()的注释,该方法将10个线程的优先级分别设为1至10,这是Java支持的10个不同级别的优先级。多次运行程序,每个线程卖出的票数都不一样,较典型的运行结果如下:
......
0号线程卖:8 张票
1号线程卖:2 张票
2号线程卖:1 张票
3号线程卖:10 张票
4号线程卖:1 张票
5号线程卖:0 张票
6号线程卖:5 张票
7号线程卖:73 张票
8号线程卖:0 张票
9号线程卖:0 张票

很明显,7号线程卖了最多的票,这是因为7号线程的优先级较高。然而8、9号线程的优先级更高,却没卖一张票。从运行结果上看,优先级起了一定的作用,但并不绝对,有时甚至不被理会。

多次改变程序中线程的数量,票的数量、卖票的延迟时间、线程的优先级等各种参数,我们试图观察线程的调度方法,但仍然无法把握Java线程的调度策略。唯一可以确定的是:它像是采用了轮转法,但不是完全的轮转,因为经常有线程被轮空好几次;它像是采用了优先级的调度策略,但又不是完全按优先级的次序分配CPU,因为最高优先级的线程也常被忽略;它有时还像先来先服务的调度。

以上我们看到的Java线程的执行过程仅仅是在Windows XP平台上的运行情况。事实上,Java在不同的发展时期、不同的平台上,其线程调度的策略也是不同的。要想概括Java的线程调度方法,不是一件容易的事,还是让我们沿着Java虚拟机实现的踪迹来探寻和理解Java线程的调度方法吧。

二.早期的JVM线程调度策略

很多网站或课本上都是这样介绍java线程的调度策略的[1]:

(1)JVM使用抢占的、基于优先权的调度策略;
(2)每个线程都有优先级,JVM总是选择最高优先级的线程执行;
(3)若果两个线程具有相同的优先级,则采用FIFO的调度顺序。

在早期的java1.1中,JVM自己实现线程调度,而不依赖于底层的平台。绿色线程(用户级线程)[2]是JVM使用的唯一的线程模型(至少是在solaris平台上),其线程调度采用的应该就是上述这种调度策略。

绿色线程的执行时间由线程本身来控制,线程自身工作告一段落后,要主动告知系统切换到另一个线程上。其特点是实现简单,不需要专门的硬件支持,切换操作对线程自身来说是预先可知的。

因为绿色线程库是用户级的,并且Solaris一次只能处理一个绿色线程,即Java运行时采用多对一的线程模型,所以会导致如下问题:(1)Java应用程序不能与Solaris环境中的多线程技术互操作,就是说Solaris管理不了Java线程;(2)Java线程不能在多处理机上并行执行;(3)Java 应用不能享用操作系统提供的并发性。由于绿色线程的这些限制,在java1.2之后的版本中放弃了该模型,而采用本地线程(Native threads,是指使用操作系统本地的线程库建立和管理的线程),即将Java线程连接到本地线程上,主要由底层平台实现线程的调度[3]。

三.依托底层平台的Java线程调度策略

Java语言规范和Java虚拟机规范是Java的重要文档,可惜的是他们都没有说明Java线程的调度问题。或许从Java的角度看,线程并不是Java最基本的内容。毕竟Thread类也仅仅是Java一个特定的类而已。

终于在Java SE 8 API规范的Thread类说明中算是找到了线程调度的有关描述:每个线程有一个优先级(从1级到10级),较高优先级的线程比低优先级线程先执行[4]。程序员可以通过Thread.setPriority(int)设置线程的优先级,默认的优先级是NORM_PRIORITY。Java SE 还声明JVM可以任何方式实现线程的优先级,甚至忽略它的存在。这可以看做Java SE提供给程序员的关于线程调度的策略,恐怕就这些了。为什么后来Java关于线程调度说得这么少?是因为它不想再管这事儿了,再说就是多嘴。

我们是通过Java创建的线程,线程调度的事儿Java是脱不开的。那Java又是如何将线程调度交给底层的操作系统去做呢?下面我们将跟随JVM虚拟机底层平台上的实现,说明Java线程的调度策略。

  1. Solaris平台上的JVM线程调度策略先说Solaris本身的线程调度。在第9版之前,Solaris 采用M:N的线程模型,即M个本地线程映射到N个内核级线程(LWP,LWP和内核级线程是一一对应的)上。当本地线程连接到一个LWP上时,它才有机会获得CPU使用权。虽然Solaris提供了改变LWP的优先权的系统调用,但是由于本地线程与LWP的连接是动态的、不固定的。一个本地线程过一会儿可能会连接到另一个LWP上。因而Solaris没有可靠的方法改变本地线程的优先权。

再说Java线程。既然Java底层的运行平台提供了强大的线程管理能力,Java就没有理由再自己进行线程的管理和调度了。于是JVM放弃了绿色线程的实现机制,将每个Java线程一对一映射到Solaris平台上的一个本地线程上,并将线程调度交由本地线程的调度程序。由于Java线程是与本地线程是一对一地绑在一起的,所以改变Java线程的优先权也不会有可靠地运行结果。

线程映射.jpg

尽管如此,Solaris早期版本还是尽量实现了基本的用户模式下的抢占。系统维护这一条戒律:就绪队列上任何线程的优先级必须小于等于正在运行的线程,否则,优先级最低的正在运行的线程将被剥夺运行的机会,即将其对应的LWP让给优先级高的本地线程。在如下三种情况下会发生线程的抢占:

(1)当正在运行的本地线程降低了其优先级,使其小于就绪队列中的某个线程的优先级
(2)当正在运行的本地线程增加了就绪队列中某个线程的优先级,使其高于正在运行的线程的优先级
(3)当就绪队列中新加入了一个优先级高于正在运行的线程的优先级,例如,某个高优先级的线程被唤醒。

Java线程的唤醒、优先级设置是由JVM实现的,但线程的调度(与LWP的连接)则是由本地线程库完成。操作系统(Solaris)可以依据自己的原则改变LWP的优先级,例如,通过动态优先级实现分时,这是线程库和JVM都无法干预的。

Solaris 9之后,使用了1:1的线程模型,即本地线程与LWP一对一地绑在一起,本地线程库也失去了直接干预线程调度的机会(指为本地线程选择连接LWP)。Java线程也就通过本地线程与LWP终生地一对一地绑在一起。这样可以通过改变本地线程或Java线程的优先级来影响LWP的优先级,从而影响系统的CPU调度。但具体的CPU分配策略还是Solaris做出的,JVM仅起辅助的作用。

  1. Windows平台上的Java线程调度策略

在Windows下,Java线程一对一地绑定到Win32线程(相当于Solaris的native线程)上。当然Win32线程也是一对一地绑定到内核级线程上,所以Java线程的调度实际上是内核完成的。Java虚拟机可以做的是通过将Java线程的优先级映射到Win32线程的优先级上,从而影响系统的线程调度决策。

Windows内核使用了32级优先权模式来决定线程的调度顺序。优先权被分为两类:可变类优先权包含了1-15级,不可变类优先权(实时类)包含了16-31级。调度程序为每一个优先级建一个调度队列,从高优先级到低优先级队列逐个查找,直到找到一个可运行的线程。

Win32将进程(process)分为如下6个优先级类:
REALTIME_PRIORITY_CLASS
HIGH_PRIORITY_CLASS
ABOVE_NORMAL_PRIORITY_CLASS
NORMAL_PRIORITY_CLASS
BELOW_NORMAL_PRIORITY_CLASS
IDLE_PRIORITY_CLASS
为区分进程内线程的优先级,每个优先级类又包含6个相对优先级:
TIME_CRITIAL
HEGHEST
ABOVE_NORNAL
NORMAL
BELOW_NORMAL
LOWEST
IDLE
这样每个Win32线程属于某个优先级类(由该线程所属的进程决定),并具有进程内的某个相对优先级,其对应的内核级线程的优先级如下表所示:

表1.JPG

当把Java 线程绑定到Win32线程时,需要将Java线程的优先级映射到Win32线程上。Java 6在Windows的实现中将Java线程的优先级按下表所示映射到Win32线程的相对优先级上。

表2.JPG

当JVM将线程的优先级映射到Win32线程的优先级上之后,线程调度的工作就是Win32和Windows内核的事儿了。

Windows采用基于优先级的、抢占的线程调度算法。调度程序保证总是让具有最高优先级的线程运行。一个线程仅在如下四种情况下才会放弃CPU:(1)被一个更高优先级的线程抢占;(2)结束;(3)时间片到;(4)执行导致阻塞的系统调用。当线程的时间片用完后,降低其优先级;当线程从阻塞变为就绪时,增加线程的优先级;当线程很长时间没有机会运行时,系统也会提升线程的优先级。Windows区分前台和后台进程,前台进程往往获得更长的时间片。以上这些措施体现了Windows基于动态优先级、分时和抢占的CPU调度策略。调度策略很复杂,考虑了线程执行过程的各个方面,再加上系统运行环境的变化,我们很难通过线程运行过程的观察理清调度算法的全貌。在本文开头的例子说明了这一点。

由于Java线程到Windows内核线程一对一的绑定方式,所以我们看到的Java线程的运行过程实际上反映的是Windows的调度策略。

请注意,尽管Windows采用了基于优先级的调度策略,但不会出现饥饿现象。其采取的主要措施是:优先级再高的的线程也会在运行一个时间片之后放弃CPU,并且降低其优先级,从而保证了低优先级线程也有机会运行。

  1. Linux中Java线程调度

同Windows一样,在Linux上Java线程一对一地映射到内核级线程上。不过Linux中是不区分进程和线程的,同一个进程中的线程可以看作是共享程度较高的一组进程。Linux也是通过优先级来实现CPU分配的,应用程序可以通过调整nice值(谦让值)来设置进程的优先级。nice值反映了线程的谦让程度,该值越高说明这个线程越有意愿把CPU让给别的线程,nice的值可以由线程自己设定。所以JVM需要实现Java线程的优先级到nice的映射,即从区间[1,10]到[19, -20]的映射。把自己线程的nice值设置高了,说明你的人品很谦让,当然使用CPU的机会就会少一点。

linux调度器实现了一个抢占的、基于优先级的调度算法,支持两种类型的进程的调度:实时进程的优先级范围为[0,99],普通进程的优先级范围为[100,140]。

表3.jpg

进程的优先权越高,所获得的时间片就越大。每个就绪进程都有一个时间片。内核将就绪进程分为活动的(active)和过期的(expired)两类:只要进程的时间片没有耗尽,就一直有资格运行,称为活动的;当进程的时间片耗尽后,就没有资格运行了,称为过期的。调度程序总是在活动的进程中选择优先级最高的进程执行,直到所有的活动进程都耗尽了他们的时间片。当所有的活动进程都变成过期的之后,调度程序再将所有过期的进程置为活动的,并为他们分配相应的时间片,重新进行新一轮的调度。所以Linux的线程调度也不会出现饥饿现象。

在Linux上,同Windows的情况类似,Java线程的调度最终转化为了操作系统中的进程调度。

四.总结

从以上Java在不同平台上的实现来看,只有在底层平台不支持线程时,JVM才会自己实现线程的管理和调度,此时Java线程以绿色线程的方式运行。由于目前流行的操作系统都支持线程,所以JVM就没必要管线程调度的事情了。应用程序通过setPriority()方法设置的线程优先级,将映射到内核级线程的优先级,影响内核的线程调度。

目前的Java的官方文档中几乎不再介绍有关Java线程的调度算法问题,因为这确实不是Java的事儿了。尽管程序中还可以调用setPriority(),提请JVM注意线程的优先级,但你千万不要把这事儿太当真。Java中所谓的线程调度仅是底层平台线程调度的一个影子而已。

由于Java是跨平台的,因此要求Java的程序设计不能对Java线程的调度方法有任何假设,即程序运行的正确性不能依赖于线程调度的方法。所以说程序员最好不要过分关心底层平台是如何实现线程调度的,呵呵!只要知道他们是并发运行的就可以了,甚至不必在意线程的优先级,因为优先级也不靠谱。正如Joshua Bloch在他的书《Effective Java》中给出的第72条忠告:任何依赖线程调度器来达到正确性或性能要求的程序,很有可能都是不可移植的[5]。当然,世界上没有绝对的事情。

如果程序员一定要规范线程的执行顺序,应该使用线程的同步操作wait(), notify()等显式实现线程之间的同步关系,才能保证程序的正确性。

参考文献:

[1] http://lass.cs.umass.edu/~shenoy/courses/fall01/labs/talab2.html[2] https://en.wikipedia.org/wiki/Green_threads[3] http://www.sco.com/developers/java/j2sdk122-001/ReleaseNotes.html#THREADS[4] http://docs.oracle.com/javase/8/docs/api/index.html[5] Joshua Bloch,Effective java

作者系山东大学计算机学院教授


作者:开点工作室
链接:https://www.jianshu.com/p/3f6b26ee51ce
 

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/wabiaozia/article/details/88544322

智能推荐

Kotlin 解压缩_kotlin 对上传的压缩包进行分析-程序员宅基地

文章浏览阅读638次。fun unZip(zipFile: String, context: Context) { var outputStream: OutputStream? = null var inputStream: InputStream? = null try { val zf = ZipFile(zipFile) val entries = zf.entries() while (en..._kotlin 对上传的压缩包进行分析

64K方法数限制解决办法_java函数大于64k编译失败-程序员宅基地

文章浏览阅读1.9k次。随着业务逻辑越来越多,业务模块也越来越大,不可避免会遇到64K方法数的限制。最直观的表现就是编译报错:较早版本的编译系统中,错误内容如下:Conversion to Dalvik format failed:Unable to execute dex: method ID not in [0, 0xffff]: 65536较新版本的编译系统中,错误内容如下:trouble writing outp_java函数大于64k编译失败

案例分享——低压电力线载波通信模组(借助电源线实现远距离数据传输、宽压输入、波特率范围广、应用场景多样化)_电力载波模块csdn-程序员宅基地

文章浏览阅读2k次,点赞7次,收藏10次。物联网领域,有很多数据通信场景,因为原设备整体系统结构、运行环境等方面的限制,需求在不增加通信数据线缆的情况下实现数据的远程传输,因为特殊应用场景下考虑到环境的限制,还不能使用常规的无线通信手段,所以借助电源线缆进行传输的电力线载波技术应运而生,本次博文给大家分享的就是博主完全自主研发的低压电力线载波通信模组。_电力载波模块csdn

密码学基础_密码体制的五个要素-程序员宅基地

文章浏览阅读7.4k次。密码学基本概念 密码学(Cryptology)是结合数学、计算机科学、电子与通信等学科于一体的交叉学科,研究信息系统安全的科学。起源于保密通信技术。具体来讲,研究信息系统安全保密和认证的一门科学。 密码编码学,通过变换消息(对信息编码)使其保密的科学和艺术 密码分析学,在未知密钥的情况下从密文推_密码体制的五个要素

python支持中文路径_基于python 处理中文路径的终极解决方法-程序员宅基地

文章浏览阅读1.9k次。1 、据说python3就没有这个问题了2 、u'字符串' 代表是unicode格式的数据,路径最好写成这个格式,别直接跟字符串'字符串'这类数据相加,相加之后type就是str,这样就会存在解码失误的问题。别直接跟字符串'字符串'这类数据相加别直接跟字符串'字符串'这类数据相加别直接跟字符串'字符串'这类数据相加unicode类型别直接跟字符串'字符串'这类数据相加说四遍3 、有些读取的方式偏偏..._python 路径 中文

阿里云 B 站直播首秀,用 Serverless 搭个游戏机?-程序员宅基地

文章浏览阅读107次。最近,阿云 B 站没声音,是在憋大招!8月5日周四 19:00 是阿里云的直播首秀,给大家请来了 Forrester 评分世界第一的 Serverless 团队产品经理江昱,给大家在线...._阿里云直播b站

随便推点

AS 3.1.3连续依赖多个Module,导致访问不到Module中的类_为什么as在一个包下建了多个module,缺无法打开了-程序员宅基地

文章浏览阅读1.1k次。我好苦啊,半夜还在打代码。还出bug,狗日的。问题是这样的:我在新建的项目里,建了两个Module: fiora-ec和fiora-core。项目的依赖顺序是这样的,App依赖fiora-ec,fiora-ec又依赖于fiora-core,因为这种依赖关系,所有可以在app和fiora-ec中删除一些不必要的引入,比如这个玩意儿:com.android.support:appcompat-v7:..._为什么as在一个包下建了多个module,缺无法打开了

Magento 常用插件二-程序员宅基地

文章浏览阅读1.4k次。1. SMTP 插件 URL:http://www.magentocommerce.com/magento-connect/TurboSMTP/extension/4415/aschroder_turbosmtp KEY:magento-community/Aschroder_TurboSmtp 2. Email Template Adapter..._magento extension pour ricardo.ch

【连载】【FPGA黑金开发板】Verilog HDL那些事儿--低级建模的资源(六)-程序员宅基地

文章浏览阅读161次。声明:本文为原创作品,版权归akuei2及黑金动力社区共同所有,如需转载,请注明出处http://www.cnblogs.com/kingst/ 2.5 低级建模的资源 低级建模有讲求资源的分配,目的是使用“图形”来提高建模的解读性。 图上是低级建模最基本的建模框图,估计大家在实验一和实验二已经眼熟过。功能模块(低级功能模块)是一个水平的长方形,而控制模块(低级控制模块)是矩形。组..._cyclone ep2c8q208c黑金开发板

R语言实用案例分析-1_r语言案例分析-程序员宅基地

文章浏览阅读2.2w次,点赞10次,收藏63次。在日常生活和实际应用当中,我们经常会用到统计方面的知识,比如求最大值,求平均值等等。R语言是一门统计学语言,他可以方便的完成统计相关的计算,下面我们就来看一个相关案例。1. 背景最近西安交大大数据专业二班,开设了Java和大数据技术课程,班级人数共100人。2. 需求通过R语言完成该100位同学学号的生成,同时使用R语言模拟生成Java和大数据技术成绩,成绩满分为100,需要满足正_r语言案例分析

Java知识体系总结(2024版),这一次带你搞懂Spring代理创建过程-程序员宅基地

文章浏览阅读639次,点赞11次,收藏26次。虽然我个人也经常自嘲,十年之后要去成为外卖专员,但实际上依靠自身的努力,是能够减少三十五岁之后的焦虑的,毕竟好的架构师并不多。架构师,是我们大部分技术人的职业目标,一名好的架构师来源于机遇(公司)、个人努力(吃得苦、肯钻研)、天分(真的热爱)的三者协作的结果,实践+机遇+努力才能助你成为优秀的架构师。如果你也想成为一名好的架构师,那或许这份Java成长笔记你需要阅读阅读,希望能够对你的职业发展有所帮助。一个人可以走的很快,但一群人才能走的更远。

车辆动力学及在Unity、UE4中的实现_unity 车辆动力学模型-程序员宅基地

文章浏览阅读3.9k次,点赞9次,收藏53次。受力分析直线行驶时的车轮受力如下:水平方向上,所受合力为:F=Ft+Fw+FfF=F_t+F_w+F_fF=Ft​+Fw​+Ff​其中,FtF_tFt​为牵引力,FwF_wFw​为空气阻力,FfF_fFf​为滚动阻力,下面我们将逐个介绍。驱动力先来说扭矩,扭矩是使物体发生旋转的一个特殊力矩,等于力和力臂的乘积,单位为N∙mN∙mN∙m:设驱动轴的扭矩为TtT_tTt​,车轮半径为rrr,那么牵引力:Ft=Tt⁄rF_t=T_t⁄rFt​=Tt​⁄r如何求得驱动轴扭矩TtT_tTt​呢?_unity 车辆动力学模型

推荐文章

热门文章

相关标签