java

位置:IT落伍者 >> java >> 浏览文章

Java多线程初学者指南(1):线程简介


发布日期:2022年05月30日
 
Java多线程初学者指南(1):线程简介

线程概述

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

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

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

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

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

线程给我们带来的好处

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

充分利用CPU资源

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

简化编程模型

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

简化异步事件的处理

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

使GUI更有效率

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

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

节约成本

提高程序的执行效率一般有三种方法

)增加计算机的CPU个数

)为一个程序启动多个进程

)在程序中使用多进程

第一种方法是最容易做到的但同时也是最昂贵的这种方法不需要修改程序从理论上说任何程序都可以使用这种方法来提高执行效率第二种方法虽然不用购买新的硬件但这种方式不容易共享数据如果这个程序要完成的任务需要必须要共享数据的话这种方式就不太方便而且启动多个线程会消耗大量的系统资源第三种方法恰好弥补了第一种方法的缺点而又继承了它们的优点也就是说既不需要购买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这个接口只有一个抽象方法run也就是Java线程模型的线程执行函数因此一个线程类的唯一标准就是这个类是否实现了Runnable接口的run方法也就是说拥有线程执行函数的类就是线程类

从上面可以看出在Java中建立线程有两种方法一种是继承Thread类另一种是实现Runnable接口并通过Thread和实现Runnable的类来建立线程其实这两种方法从本质上说是一种方法即都是通过Thread类来建立线程并运行run方法的但它们的大区别是通过继承Thread类来建立线程虽然在实现起来更容易但由于Java不支持多继承因此这个线程类如果继承了Thread就不能再继承其他的类了因此Java线程模型提供了通过实现Runnable接口的方法来建立线程这样线程类可以在必要的时候继承和业务有关的类而不是Thread类

上一篇:Java多线程的相关机制

下一篇:Java多线程(五)之BlockingQueue深入分析