概括性的说明:一个进程是一个独立的运行环境,可以被看做一个程序或者一个应用。而线程是在进程中执行的一个任务。线程可以被称为轻量级进程。线程需要较少的资源来创建和驻留在进程中,并且可以共享进程中的资源。线程是调度的基本单位,而进程是资源分配的基本单位。线程切换的代价要小于进程切换的代价。
一、进程
1、进程概念
迄今为止对进程这一概念还没有一个确切统一的描述。有人称进程是可以并行运动的计算部分;有人称进程是一个程序与其数据一道在计算机上顺序执行时所产生的活动;有人从调度组织角度出发,称进程是一个可以独立调度的活动;有人则从资源共享和竞争方面观察,认为进程是一个抽象的实体,当它执行一个任务时将要求分配和释放各种资源。我们对进程作如下描述:进程是处于运行状态的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位。
2、进程与程序的区别与联系
进程是程序的一次运行活动,属于一种动态的概念。程序是一组有序的静态指令,是一种静态的概念。但是,进程离开了程序也就没有了存在的意义。因此,我们可以这样说:进程是执行程序的动态过程,而程序是进程运行的静态文本。
一个进程可以执行一个或多个程序。反之,同一程序也可能由多个进程同时执行。
程序可以作为一种软件资源长期保持着,而进程则是一次执行过程,它是暂时的,是动态地产生和终止的。
3、进程的结构
进程通常由三部分组成。一部分是程序,一部分数据集合,另一部分被称为进程控制块(PCB)。进程的程序部分描述了进程所要完成的功能。数据集合部分则有两方面的内容,即程序运行时所需要的数据部分和工作区。如果一个程序能为多个进程同时共享执行,它是进程执行时不可修改的部分。而数据集合部分则通常为一个进程独占,为进程的可修改部分。程序和数据集合是进程存在的物质基础,是进程的实体。
进程控制块(PCB)有时也称为进程描述块,它包含了进程的描述信息和控制信息,是进程动态特性的集中反映,也是进程存在的唯一标识。PCB一般都应包含如下信息:
(1)进程标识符(内部,外部)
(2)处理机的信息(通用寄存器,指令计数器,PSW,用户的栈指针)。
(3)进程调度信息(进程状态,进程的优先级,进程调度所需的其它信息,事件)
(4)进程控制信息(程序的数据的地址,资源清单,进程同步和通信机制,链接指针)
二、线程
1、线程概念
不同的进程有不同进程空间,进程之间的切换消耗比较大。因此就考虑到引入线程的概念,在进程的内部引入并发性,一个进程可以创建多个线程,线程之间具备并发性。不同的线程之间可以共享进程的地址空间和数据。
线程是一个程序,或者进程内部的一个顺序控制流。线程本身不能独立运行,必须在进程中执行,使用进程的地址空间。每个线程有自己单独的程序计数器。
每个程序执行时都会产生一个进程,而每一个进程至少要有一个主线程。除了主线程外你还可以给进程增加其它的线程,多线程就是在一个进程内有多个线程,从而使一个应用程序有了多任务的功能。总之,进程内的同一类线程可以共享代码和数据空间,每个线程有独立的运行栈和程序计数器,切换的开销比较小,灵活性高。
2、创建多线程
Java 定义了一个线程的概念模型,把一个线程分为三部分:虚拟CPU(java.lang.Thread 类),虚拟CPU 执行的代码和数据。
创建一个 java.lang.Thread 的对象,就意味着创建了一个线程。一个由main 方法开始执行的Java 程序,至少包含一个线程,即主线程。创建多个Thread 的对象,就创建了多个线程。
Thread 类通过其run()方法来完成起任务,方法run()为线程体。一般在java 中有两种比较典型的构造线程的方法:1)继承Thread 类,重写run()方法;2)把线程体从Thread类中独立出来,形成单独的线程目标对象,就是实现Runnable 接口及其run()方法。这两种方法都是通过 Thread 类的start()方法启动线程的。
三、线程池
在Java中使用线程池一般分为3 步:1)创建线程目标对象,可以是不同的;2)使用Executors 创建线程池,返回一个ExecutorService类型的对象;3)使用线程池执行线程目标对象,最后,结束线程池中的线程。
在java.util.concurrent.Executors类中定义了一些工厂方法和工具方法,其中最重要的就是创建各种线程池。
1) public static ExecutorService newFixedThreadPool(int nThreads)
创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程,在需要时使用提供的 ThreadFactory 创建新线程。在任意点,在大多数 nThreads 线程会处于处理任务的活动状态。如果在所有线程处于活动状态时提交附加任务,则在有可用线程之前,附加任务将在队列中等待。如果在关闭前的执行期间由于失败而导致任何线程终止,那么一个新线程将代替它执行后续的任务(如果需要)。在某个线程被显式地关闭之前,池中的线程将一直存在。
2) public static ThreadFactory defaultThreadFactory()
返回用于创建新线程的默认线程工厂。此工厂创建同一个线程组(ThreadGroup)中Executor 使用的所有新线程。如果有SecurityManager,则它使用System.getSecurityManager()返回的组,其他情况则使用调用defaultThreadFactory 方法的组。每个新线程都作为非守护程序而创建,并且具有设置线程优先级为Thread.NORM_PRIORITY 与线程组中允许的最大优先级的较小者。新线程具有可通过pool-N-thread-M 的 Thread.getName() 来访问的名称,其中N 是此工厂的序列号,M 是此工厂所创建线程的序列号。
3) public static ExecutorService newCachedThreadPool()
创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。对于执行很多短期异步任务的程序而言,这些线程池通常可提高程序性能。调用 execute 将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。因此,长时间保持空闲的线程池不会使用任何资源。注意,可以使用 ThreadPoolExecutor 构造方法创建具有类似属性但细节不同(例如超时参数)的线程池。
5) void execute(Runnable command)
在未来某个时间执行给定的命令。该命令可能在新的线程、已入池的线程或者正调用的线程中执行,这由 Executor 实现决定。
6) void shutdown()
启动一次顺序关闭,执行以前提交的任务,但不接受新任务。如果已经关闭,则调用没有其他作用。
四、线程的基本控制
线程创建后,可以执行start()方法启动线程,根据线程任务的特性和线程之间的协调性要求,需要对线程进行控制。对线程的控制通常是通过调用Thread 对象的方法实现的,主要有sleep()、suspend()、resume()、join()、interrupt()和stop 方法。一般情况下方法的调用会引起线程状态的转变。
1、sleep()
Thread.sleep()使当前线程的执行暂停一段指定的时间,这可以有效的使应用程序的其他线程或者运行在计算机上的其他进程可以使用处理器时间。该方法不会放弃除CPU 之外的其它资源。
Sleep 有两个重载的版本,一个以毫秒指定睡眠时间,另一个以纳秒指定睡眠时间,但并不保证这些睡眠时间的精确性,因为他们受到系统计时器和调度程序精度和准确性的影响。另外中断(interrupt)可以终止睡眠时间,在任何情况下,都不能假设调用sleep 就会按照指定的时间精确的挂起线程。
2、join()
Join 方法让一个线程等待另一个线程的完成,如果t1,t2是两个Thread 对象,在t1中调用t2.join(),会导致t1 线程暂停执行,直到t2 的线程终止。Join 的重载版本允许程序员指定等待的时间,但是和sleep 一样,这个时间是不精确的。
3、interrupt()
有三种方法可以使线程终止:1)run()方法正常返回;2)run()方法意外结束;3)应用程序终止。我们可以通过中断(Thread.interrupt)线程来请求取消,并且让线程来监视并响应中断。中断请求通常是用户希望能够终止线程的执行,但并不会强制终止线程,但是它会中断线程的睡眠状态,比如调用sleep 和wait 方法后。线程自己检查中断状态并终止线程比直接调用 stop()放要安全很多,因为线程可以保存自己的状态。并且stop()方法已经不推荐使用了。如果被中断的线程正在执行 sleep,或者wait 方法,就会抛出InterruptedException 异常。这种抛出异常的中断会清除线程的中断状态。
在 Thread 类中提供了Stop 方法了强迫线程停止执行。但是现在已经过时了,,因此此处不予介绍。
参考《Java并发编程实践》01章