首页 > 代码库 > 多线程
多线程
java是支持多线程的语言之一,它可以让不同程序块同时运行,这样可以让程序运行得更为流畅,性能也更高,同时也可以达到多任务处理的目的。
我们首先要了解一些概念:
什么是线程?
线程,有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。由于线程之间的相互制约,致使线程在运行中呈现出间断性。线程也有就绪、阻塞和运行三种基本状态。每一个程序都至少有一个线程,若程序只有一个线程,那就是程序本身。
什么是多线程?
在单个程序中同时运行多个线程完成不同的工作,称为多线程。
什么是进程?
进程是60年代初首先由麻省理工学院的MULTICS系统和IBM公司的CTSS/360系统引入的。
进程是一个具有独立功能的程序关于某个数据集合的一次运行活动。它可以申请和拥有系统资源,是一个动态的概念,是一个活动的实体。它不只是程序的代码,还包括当前的活动,通过程序计数器的值和处理寄存器的内容来表示。
线程和进程的关系?
线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。
用一个比喻来形容:
计算机的核心是CPU,它承担了所有的计算任务。它就像一座工厂,时刻在运行。假定工厂的电力有限,一次只能供给一个车间使用。也就是说,一个车间开工的时候,其他车间都必须停工。背后的含义就是,单个CPU一次只能运行一个任务。进程就好比工厂的车间,它代表CPU所能处理的单个任务。任一时刻,CPU总是运行一个进程,其他进程处于非运行状态。一个车间里,可以有很多工人。他们协同完成一个任务。线程就好比车间里的工人。一个进程可以包括多个线程。车间的空间是工人们共享的,比如许多房间是每个工人都可以进出的。这象征一个进程的内存空间是共享的,每个线程都可以使用这些共享内存。可是,每间房间的大小不同,有些房间最多只能容纳一个人,比如厕所。里面有人的时候,其他人就不能进去了。这代表一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。一个防止他人进入的简单方法,就是门口加一把锁。先到的人锁上门,后到的人看到上锁,就在门口排队,等锁打开再进去。这就叫"互斥锁"(Mutual exclusion,缩写 Mutex),防止多个线程同时读写某一块内存区域。还有些房间,可以同时容纳n个人,比如厨房。也就是说,如果人数大于n,多出来的人只能在外面等着。这好比某些内存区域,只能供给固定数目的线程使用。这时的解决方法,就是在门口挂n把钥匙。进去的人就取一把钥匙,出来时再把钥匙挂回原处。后到的人发现钥匙架空了,就知道必须在门口排队等着了。这种做法叫做"信号量"(Semaphore),用来保证多个线程不会互相冲突。不难看出,mutex是semaphore的一种特殊情况(n=1时)。也就是说,完全可以用后者替代前者。但是,因为mutex较为简单,且效率高,所以在必须保证资源独占的情况下,还是采用这种设计。
我们为什么要使用线程?
在 Java 程序中使用线程有许多原因。如果您使用 Swing、servlet、RMI 或 Enterprise JavaBeans(EJB)技术,您也许没有意识到您已经在使用线程了。
使用线程的一些原因是它们可以帮助:
- 使 UI 响应更快
- 利用多处理器系统
- 简化建模
- 执行异步或后台处理
详细解释如下:
响应更快的 UI
事件驱动的 UI 工具箱(如 AWT 和 Swing)有一个事件线程,它处理 UI 事件,如击键或鼠标点击。
AWT 和 Swing 程序把事件侦听器与 UI 对象连接。当特定事件(如单击了某个按钮)发生时,这些侦听器会得到通知。事件侦听器是在 AWT 事件线程中调用的。
如果事件侦听器要执行持续很久的任务,如检查一个大文档中的拼写,事件线程将忙于运行拼写检查器,所以在完成事件侦听器之前,就不能处理额外的 UI 事件。这就会使程序看来似乎停滞了,让用户不知所措。
要避免使 UI 延迟响应,事件侦听器应该把较长的任务放到另一个线程中,这样 AWT 线程在任务的执行过程中就可以继续处理 UI 事件(包括取消正在执行的长时间运行任务的请求)。
利用多处理器系统
多处理器(MP)系统比过去更普及了。以前只能在大型数据中心和科学计算设施中才能找到它们。现在许多低端服务器系统 ― 甚至是一些台式机系统 ― 都有多个处理器。
现代操作系统,包括 Linux、Solaris 和 Windows NT/2000,都可以利用多个处理器并调度线程在任何可用的处理器上执行。
调度的基本单位通常是线程;如果某个程序只有一个活动的线程,它一次只能在一个处理器上运行。如果某个程序有多个活动线程,那么可以同时调度多个线程。在精心设计的程序中,使用多个线程可以提高程序吞吐量和性能。
简化建模
在某些情况下,使用线程可以使程序编写和维护起来更简单。考虑一个仿真应用程序,您要在其中模拟多个实体之间的交互作用。给每个实体一个自己的线程可以使许多仿真和对应用程序的建模大大简化。
另一个适合使用单独线程来简化程序的示例是在一个应用程序有多个独立的事件驱动的组件的时候。例如,一个应用程序可能有这样一个组件,该组件在某个事件之后用秒数倒计时,并更新屏幕显示。与其让一个主循环定期检查时间并更新显示,不如让一个线程什么也不做,一直休眠,直到某一段时间后,更新屏幕上的计数器,这样更简单,而且不容易出错。这样,主线程就根本无需担心计时器。
异步或后台处理
服务器应用程序从远程来源(如套接字)获取输入。当读取套接字时,如果当前没有可用数据,那么对 SocketInputStream.read()
的调用将会阻塞,直到有可用数据为止。
如果单线程程序要读取套接字,而套接字另一端的实体并未发送任何数据,那么该程序只会永远等待,而不执行其它处理。相反,程序可以轮询套接字,查看是否有可用数据,但通常不会使用这种做法,因为会影响性能。
但是,如果您创建了一个线程来读取套接字,那么当这个线程等待套接字中的输入时,主线程就可以执行其它任务。您甚至可以创建多个线程,这样就可以同时读取多个套接字。这样,当有可用数据时,您会迅速得到通知(因为正在等待的线程被唤醒),而不必经常轮询以检查是否有可用数据。使用线程等待套接字的代码也比轮询更简单、更不易出错。
使用线程我们需要注意些什么呢?
- 当多个线程访问同一数据项(如静态字段、可全局访问对象的实例字段或共享集合)时,需要确保它们协调了对数据的访问,这样它们都可以看到数据的一致视图,而且相互不会干扰另一方的更改。
- 虽然线程可以大大简化许多类型的应用程序,过度使用线程可能会危及程序的性能及其可维护性。线程消耗了资源。因此,在不降低性能的情况下,可以创建的线程的数量是有限制的。
了解了这些基本概念,下面我们来学习多线程的具体运用:
LZ在博客园上看到一篇比较详细的文章,推荐给大家《java 多线程总结》,自己就偷懒一下下,呵呵。
相信大家再看了这篇文章之后能有一个基本的了解。
参考文献:《IBM官网》、《阮一峰的网络日志》、《百度百科》