Java

【Java 深入JVM】第2篇 – 了解线程

微信扫一扫,分享到朋友圈

【Java 深入JVM】第2篇 – 了解线程
收藏 0 1

一、线程概述

线程是程序运行的基本执行单元。当操作系统(不包括单线程的操作系统,如早期的DOS)在执行一个程序时,会在系统中建立一个进程,而在这个进程中,必须至少建立一个线程(这个线程被称为主线程)来作为这个程序运行的入口点。因此,在操作系统中运行任何程序都至少有一个主线程。

进程与线程是现代操作系统中两个必不可少的运行模型。在操作系统中可以有多个进程,这些进程包括系统进程(有操作系统内部建立的进程)和用户进程(由用户程序建立的进程);一个进程中可以有一个或多个线程。进程与进程之间不共享内存,也就是说系统中的进程是在各自独立的内存空间中运行的。而一个进程中的线程可以共享系统分派给这个进程的内存空间。

线程不仅可以共享进程的内存,而且还拥有一个属于自己的内存空间,这段内存空间也叫做线程栈,是在建立线程时由系统分配的,主要用来保存线程内部所使用的数据,如线程执行函数中所定义的变量。

但是,需要注意的是,任何一个线程在建立的时候都会执行一个函数,这个函数叫做线程执行函数。也可以将这个函数看做线程的入口点(类似于程序中的main函数)。无论使用什么语言或技术来建立线程,都必须执行这个函数(这个函数的表现形式可能不一样,但都会有一个这样的函数)。如在Windows中用于建立线程的API函数CreateThread的第三个参数就是这个执行函数的指针。

在操作系统将进程分成多个线程后,这些线程可以在操作系统的管理下并发执行,从而大大提高了程序的运行效率。虽然线程的执行从宏观上看,是多个线程同时执行,但实际上这只是操作系统的障眼法。由于一块CPU同一时间点只能执行一条指令,一次,在拥有一块CPU的计算机上不可能同时执行两个任务。而操作系统为了能提高程序的运行效率,在一个线程空闲时会撤下这个线程,并且会让其他的线程来执行,这种方式叫做线程调度。我们之所以从表面上看上去像是多个线程在同时执行,是因为不同线程之间切换的事件非常的短,而且在一般情况下切换的非常频繁。假设我们有线程A和B。在运行时,可能是A执行了1毫秒,便切换到了B,B执行了1毫秒,有切换到了A,而A有执行了1毫秒。由于1毫秒的时间差对于人类来说是很难感知到的,因此,从便面上看就像A和B在同时运行一样,但实际上A和B是交替运行的。

二、线程的优势

如何能合理的使用线程,将会减少开发和维护成本,甚至可以改善复杂应用程序的性能。如在GUI应用程序中,还可以通过线程的异步特性来更好的处理事件;在应用服务器程序中可以通过建立多个线程来处理客户端的请求。线程甚至还可以简化虚拟机的实现,如Java虚拟机(JVM)的垃圾回收期(Garbage Collector)通常运行在一个或多个线程中。因此,使用线程将会从以下五个方面来改善我们的应用程序:

1、充分利用CPU资源

现在世界上大多数计算机只有一块CPU。因此,充分利用CPU资源显得尤为重要。当执行单线程程序时,有在程序发生阻塞时CPU可能会处于空闲状态。这将造成大量的计算资源的浪费。而在程序中使用多线程可以在某一个线程处于休眠或阻塞,而CPU又恰好处于空闲状态时来运行其他的线程。这样CPU就很难有空闲的时候。因此,CPU资源就的到了充分的利用。

2、简化编程模型

如何程序员只完成一项任务,那只要写一个单线程的程序,并且按着执行这个任务的步骤编写代码即可。但要完成多项任务,如果还使用单线程的话,那就得在程序中判断没项任务是否应该执行以及什么时候执行。如显示一个时钟的时、分、秒三个指针。使用单线程就得在循环中逐一判断这三个指针的转动时间和角度。如果使用三个线程分别来处理这三个指针的显示,那么对于每个线程来说就是执行一个单独的任务。这样有助于开发人员对程序的理解和维护。

3、简化异步时间的处理

当一个服务器应用程序在接收不同的客户端连接时最简单的处理方法就是为每一个客户端连接建立一个线程。然后监听线程任然负责监听来自客户端的请求。如果这种应用程序采用单线程来处理,当监听线程接收到一个客户端请求后,开始读取客户端发来的数据,在读完数据后,read方法处于阻塞状态,也就是说,这个线程将无法在监听客户端请求了。而要想在单个线程中处理多个客户端请求,就必须使用非阻塞的Socket连接和异步I/O。但使用异步I/O方式比使用同步I/O方式更难以控制,也更容易出错。因此,使用多线程和同步I/O可以更容易的处理类似于多请求的异步事件。

4、使用GUI更有效率

使用单线程来处理GUI事件时,必须使用循环来对随时可能发生GUI事件进行扫描,在循环内部除了扫描GUI事件外,还要来执行其他的程序代码。如何这些代码太长,那么GUI事件就会被“冻结”,直到这些代码被执行完为止。

例如,Java的GUI框架(如SWING、AWT和SWT)中都使用了一个单独的事件分派线程(event dispatch thread,EDT) 来对GUI事件进行扫描。当我们按下一个按钮时,按钮的单击事件函数会在这个事件分派线程中被调用。由于EDT的任务只是对GUI事件进行扫描,因此,这种方式对事件的反应是非常快的。

5、节约成本

提高程序的执行效率一般有三种方法:
(1) 增加计算机的CPU个数。
(2) 为一个程序启动多个进程
(3) 在程序中使用多进程
第一种方法是最容易做到的,但同时也是最昂贵的。这种方法不需要修改程序,从理论上来说,任何程序可以用用这种方法来提高执行效率。那就是提升硬件配置,如换多核心多线程的CPU,加大内存空间等。

第二种方法虽然不用购买新的硬件,但这种方式不容易共享数据,如果这个程序要完成的任务必须要共享数据的话,那将会很麻烦。

而启动多个线程会消耗大量的系统资源,第三种方法恰好弥补了第一种方法的缺点,而又继承了他们的有点。也就是说,既不需要提升硬件,也不会应为启用太多的线程而占用大量的系统资源(在默认情况下,一个线程所占用的内存空间要元比一个进程所占用的内存空间小得多),并且多线程可以模拟多块CPU的运行方式,因此,使用多线程是提高程序执行效率的最廉价的方式。

三、Java的线程模型

由于Java是纯面向对象语言,因此,Java的线程模型也是面向对象的。Java通过Thread类将线程所必须的功能都封装了起来。要想创建一个线程,必须要有一个线程执行函数,这个线程执行函数对应Thread类的run方法。Thread类还有一个start方法,这个方法负责创建线程,相当于调用windows的建立函数CreateThread。当调用start方法后,如果线程创建成功,并自动调用Thread类的run方法。因此,任何继承Thread的Java类都可以通过Thread类的start方法来创建线程。如果想运行自己的线程执行函数,那就覆盖Thread类的run方法。

在Java的线程模型中除了Thread类,还有一个标识某个Java类是否可作为线程类的接口Runnable,也就是Java线程模型的线程执行函数。因此,一个线程类的唯一标准就是这个类是否实现了Runnable接口的run方法,也就是说,拥有线程执行函数的类就是线程类。

从以上所述可以看出,在Java中创建线程有两种常用方式,一种是继承Thread类,另一种是实现Runnable接口(还有其他方式,这里不做介绍),但从本质上来说,其实就是一种方法,最终都是通过Thread类来创建线程,只不过介于Java的单继承的限制,可以在必要的时候去继承其的业务类,使用接口来创建线程,这样既可以实现线程的创建,也不影响继承其他的业务功能。

实际上线程是指程序执行过程中的一个线程实体。JVM 允许一个应用并发执行多个线程。

Hotspot JVM 中的Java 线程与原生操作系统线程有直接的映射关系。

当线程本地存储、缓冲区分配、同步对象、栈、程序计数器等准备好以后,就会创建一个操作系统原生线程。Java 线程结束,原生线程随之被回收。操作系统负责调度所有线程,并把它们分配到任何可用的CPU 上。当原生线程初始化完毕,就会调用Java 线程的run() 方法。当线程结束时,会释放原生线程和Java 线程的所有资源。

HotspotJVM 后台运行的系统线程主要有下面几个:

本站内容仅用于个人学习、研究或欣赏。本站不保证内容的正确性。通过使用本站内容随之而来的风险与本站无关 ,内容大部分源自于站长工作学习、看过、读过的知识,部分内容源自于网络、部分源自于自己的理解、也有部分为原创,若有侵权,请及时通知本站,予以删除。 联系邮箱:1456726508@qq.com

为自己努力,为亲人拼搏,为幸福奋斗。
上一篇

【Java 深入JVM】第1篇 - 了解JVM

下一篇

【Java 深入JVM】第3篇 – 内存区域

你也可能喜欢

1 条评论

发表评论

插入图片

分类目录

微信扫一扫

微信扫一扫