摘要:线程的可能实现方式基本上主流的操作系统都支持线程,也提供了线程的实现。使用用户线程和内核线程混合实现在这种混合实现下,既存在用户线程,也存在内核线程。
进程与线程
在传统的操作系统中,最核心的概念是“进程”,进程是对正在运行的程序的一个抽象。
进程的存在让“并行”成为了可能,在一个操作系统中,允许运行着多个进程,这些进程“看起来”是同时在运行的。
如果我们的计算机同时运行着 web 浏览器、电子邮件客户端、即时通讯软件例如QQ微信等多个进程,我们感觉这些进程都是同时在运行的,假设这台计算机搭配的是多个 CPU 或者 多核 CPU,那么这种多个进程并行的现象可能一点也不奇怪,完全可以为每个进程多带带分配一个 CPU,这样就实现了多进程并行。
然而事实上,在计算机只有一个 CPU 的情况下,它也能给人类一种感觉:多个进程同时在运行。但人类的感觉往往是比较模糊的,不精确的。事实是由于 CPU 的计算速度非常地快,它能快速地在各个进程之间切换,在某一瞬间,CPU 只能运行一个进程,但一秒钟之内,它就能通过快速切换,让人产生多个进程同时在运行的错觉。
在操作系统中,为什么在进程的基础上,又衍生出了线程的概念呢?
由于对于一些进程而言,它内部会发生多种活动,有些活动可能会在某个时间里阻塞,有些活动不会,如果通过线程将这些活动分离开使它们能够并行地运行,则设计程序的时候会更加简单。
线程比进程的创建更加轻量级,性能消耗更少
如果一个进程既需要 CPU 计算,也需要I/O处理,拥有多线程允许这些活动重叠进行,加快整个进程的执行速度。
每一个进程在操作系统中都拥有独立的一块内存地址空间,该进程创建的所有线程共享这块内存,支持多线程的操作系统,会让线程作为 CPU 调度的最小单位。CPU 的时间片在不同的线程之间进行分配。
线程的可能实现方式基本上主流的操作系统都支持线程,也提供了线程的实现。而 Java 语言为了应对不同硬件和操作系统的差异,提供了对线程操作的统一抽象,在 Java 中我们使用 Thread 类来代表一个线程。
Thread 的具体实现可能会有不同的实现方式:
内核线程是操作系统内核支持的线程,在内核中有一个线程表用来记录系统中的所有线程,创建或者销毁一个线程时,都需要涉及到系统调用,然后再内核中对线程表进行更新操作。对内核线程的阻塞以及其它操作,都涉及到系统调用,系统调用的代价都比较大,涉及到在用户态和内核态之间的来回切换。此外,内核内部有线程调度器,用于决定应该将 CPU 时间片分配个哪个线程。
程序一般不会直接操作内核线程,而是使用内核线程的一种高级接口,轻量级进程。轻量级进程与内核线程之间的关系是 1:1,每一个轻量级进程内部都有一个内核线程支持。
上图中, LWP 指 Light Weight Process,即轻量级进程;KLT 指 Kernel Level Thread,即内核线程。
使用用户线程实现用户线程是程序或者编程语言自己实现的线程库,系统内核无法感知到这些线程的存在。用户线程的建立、同步、销毁和调度,都在用户态中完成,无须内核的帮助,不需要进行系统调用,这样的好处是对于线程的操作是非常高效的。在这种情况下,进程和用户线程的比例是 1 :N。
用户态线程面对如何阻塞线程时,会面临困难,阻塞一个用户态线程会出现把整个进程都阻塞的情况,多线程也就失去了意义。因为缺少内核的支持,所以很多需要利用内核才能完成的工作,例如阻塞与唤醒线程、多 CPU 环境下线程的映射等,都需要用户程序去实现,实现起来会异常困难。
使用用户线程和内核线程混合实现在这种混合实现下,既存在用户线程,也存在内核线程。用户态线程的创建、切换这些操作依然很高效,并且用户态实现的线程,比较容易加大线程的规模。需要操作系统内核支持的功能,则通过内核线程来做到,例如映射到不同的处理器上、处理线程的阻塞与唤醒以及内核线程的调度等。这种实现依然会使用到轻量级进程 LWP,它是用户线程和内核线程之间的桥梁。
Java 线程的实现在 JDK1.2 之前, Java 的线程是使用用户线程实现的,在 JDK1.2 之后,Java 才采用操作系统原生支持的线程模型来实现,Java 线程模型的实现方式,主要取决于操作系统。对于 Oracle JDK 来说,在 Windows 和 Linux 上的线程模型是采用一对一的方式实现的,即一条 Java 线程映射到一条轻量级进程(内核线程)。而在 Solaris 平台中,Java 则支持 1 :1 和 N : M 的线程模型。
线程的阻塞与等待Java 中的线程的状态有以下几种:New,Runnable,Waiting, TimedWaiting, Blocked,Terminated。
NEW:线程初创建,未运行
RUNNABLE:线程正在运行,但不一定消耗 CPU
BLOCKED:线程正在等待另外一个线程释放锁
WAITING:线程执行了 wait, join, LockSupport.park() 方法
TIMED_WAITING:线程调用了sleep, wait, join, LockSupport.parkNanos() 等方法,与 WAITING 状态不同的是,这些方法带有表示时间的参数。
其中 Blocked 和 Waiting 有个重要的区别是,阻塞(Blocked)状态的线程在等待获取一个排他锁,例如线程在等待进入一个synchronized关键字包围的临界区时,就进入 Blocked 状态。而 Waiting 状态则是在等待被唤醒,或者等待一段时间。
参考资料《深入理解 Java 虚拟机》第二版 - 周志明
《现代操作系统》第四版 - Andrew S. Tanenbaum
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/77139.html
摘要:大多数待遇丰厚的开发职位都要求开发者精通多线程技术并且有丰富的程序开发调试优化经验,所以线程相关的问题在面试中经常会被提到。掌握了这些技巧,你就可以轻松应对多线程和并发面试了。进入等待通行准许时,所提供的对象。 最近看到网上流传着,各种面试经验及面试题,往往都是一大堆技术题目贴上去,而没有答案。 不管你是新程序员还是老手,你一定在面试中遇到过有关线程的问题。Java语言一个重要的特点就...
摘要:并发编程实战水平很高,然而并不是本好书。一是多线程的控制,二是并发同步的管理。最后,使用和来关闭线程池,停止其中的线程。当线程调用或等阻塞时,对这个线程调用会使线程醒来,并受到,且线程的中断标记被设置。 《Java并发编程实战》水平很高,然而并不是本好书。组织混乱、长篇大论、难以消化,中文翻译也较死板。这里是一篇批评此书的帖子,很是贴切。俗话说:看到有这么多人骂你,我就放心了。 然而知...
摘要:例子如下可以用如下方式创建并运行上述子类一旦线程启动后方法就会立即返回,而不会等待到方法执行完毕才返回。但是,事实上方法并非是由刚创建的新线程所执行的,而是被创建新线程的当前线程所执行了。这是因为线程是并行执行而非顺序的。 showImg(http://segmentfault.com/img/bVbN5u); Java线程类也是一个object类,它的实例都继承自java.lang...
阅读 3024·2021-10-12 10:20
阅读 2784·2021-09-27 13:56
阅读 762·2021-09-27 13:36
阅读 1396·2021-09-26 09:46
阅读 2397·2019-08-30 14:02
阅读 2666·2019-08-28 18:14
阅读 1236·2019-08-26 10:32
阅读 1663·2019-08-23 18:25