Java中的线程

来源:互联网 发布:it之家论坛 编辑:程序博客网 时间:2024/06/05 15:46
转载请标明出处:http://blog.csdn.net/zhangxingping

Java中的线程

进程和线程

在并发性程序中,有两个基本的执行单元:进程和线程。在Java编程语言中,并发编程大多数情况下都是和线程相关。然而,进程也是很重要的。

 

一个计算机系统中通常都有很多活动的进程和线程。这一点即使是在只有一个执行核心,并且在给定时刻只能执行一个线程的系统中都是存在的。单一核心的处理时间是由整个操作系统的“时间片”特性来在众多的进程和线程中共享的。

 

现在,计算机系统中有多个处理器或者是有多核处理器的情况越来越普遍。这就大大增强了系统执行多个进程和线程的并发性。

进程

一个进程就是一个独立的执行环境。进程有着完整的,私有的基本的运行时资源,尤其是每个进程都有自己的内存空间。

 

进程通常会被看作是程序或者是应用程序的同义词。然而,用户看到的应用程序实际上可能是多个相互协作的进程。为了方便进程间通讯,绝大多数的操作系统都支持IPC(Inter Process Communication , 进程间通讯),诸如管道(pipe)和套接字(socket)。 IPC不仅可用于统一系统中进程间的相互通讯,还可以用于不同系统间的进程通讯。

 

大多数的java虚拟机的实现都是作为一个单独的进程的。通过使用ProcessBuilder,Java应用程序可以创建额外的进程。多进程的应用程序超出了本节讨论的范围。

 

线程

线程有时被称为是轻型的进程。进程和线程都提供了一种运行环境。但是创建一个新的线程比创建一个新的进程需要更少的资源。

 

线程存在于进程之中——每个进程中至少有一个线程。同一进程的多个线程间共享进程的资源,包括内存和文件。这样做是出于效率的考虑,但是可能带来潜在的通信问题。

 

多线程是Java平台的一个重要特性。如果我们将进行诸如内存管理和信号处理的线程算上的“系统”线程计算上的话,那么每一个应用程序至少都有一个线程,或者是多个线程。但是从应用程序的编程人员的角度来看,我们总是从一个叫做主线程的线程开始。该线程能够创建其他的线程,这点我们会在下一节中进行讨论。

 

线程对象

每一个线程都是和类Thread的实例相关联的。在Java中,有两种基本的使用Thread对象的方式,可用来创建并发性程序。

  • 在应用程序需要发起异步任务的时候,只要生成一个Thread对象即可,这样可以直接控制线程的创建并对其进行管理。
  •  把应用程序的任务交给执行器(executor),这样可以将对线程的管理从程序中抽离出来。

本节将对使用Thread对象这种方式进行讨论。采用执行器的方式可以参阅高级的并发性对象(high-levelconcurrency objects)。

定义并启动一个线程

创建线程的应用程序必须提供线程运行时的代码。有两种方式:

  • 提供一个可运行的对象(Runnableobject)。接口Runnable中定义了唯一的方法:run, 意思就是说其中含有运行时需要执行的代码。该Runnable对象被传递给Thread的构造函数,如下:
public classHelloRunnable implements Runnable {    public void run() {        System.out.println("Hello from athread!");    }     public static void main(String args[]) {        (new Thread(newHelloRunnable())).start();    } }


  • 继承Thread类。类Thread本身已经实现了Runnable,尽管其实现中什么也没有做。应用程序中,可以继承该类,并提供自己的run实现,如下:
public class HelloThread extends Thread {     public void run() {        System.out.println("Hello from a thread!");    }     public static void main(String args[]) {        (new HelloThread()).start();    } }

—————————————————————————————————————————————————————————————————————————————

注意:上面两个示例程序中都调用了Thread.start来启动一个新的线程。—————————————————————————————————————————————————————————————————————————————

那我们到底应该选用上面两种方式中的哪种呢?第一种方式更通用一些,因为Runnable对象可以是从Thread类之外的别的类派生而来的。而第二种方式在一些简单的程序中用起来则更方法;但是这种方式中线性了我们的任务类只能是从Thread派生而来的(可以直接派生,也可以是间接派生)。本书将聚焦于第一种方式,也就是将可运行的任务和执行该任务的Thread对象向分离的这种方式。这不仅仅是因为这种方式更灵活,还由于这种方式更适合我们后面要讨论的用于管理线程的高级API。

 

类Thread中定义了一些用于线程管理的方法。这其中就有一些是静态方法。他们可用来获取调用这些方法的线程的信息;或者是用来修改调用这些方法的线程的状态。而其他的一些方法则由涉及到对这些线程和Thread对象进行管理的线程来调用。我们将会在接着的小节中对其中的一些方法进行讨论。

 

使用sleep来暂停线程的执行

调用方法Thread.sleep可以让当前线程挂起一个指定的时间段。这是一种让进程的其他线程得到处理器时间的一个有效手段;或者是系统中其他应用程序得到处理器时间的一个很好方式。该方法还可用协调节奏,正如下面的实例程序中的那样,可用来等待别的需要一定时间要求来处理其任务的线程。

 

该方法有着两种重载形式:以毫秒为单位指定睡眠时间和以纳秒为单位指定睡眠时间。但是,其中指定的时间并不能保证很精确。这是因为这点取决于操作系统。另外,睡眠是可以通过中断而结束的,这点我们将会在本小节的后面看到。总之,我们不能认为调用该方法就可以确保线程被精确地挂起指定的时间长度。

 

示例程序SleepMessages中使用sleep来实现间隔4秒钟打印消息:

public class SleepMessages {    public static void main(String args[])        throws InterruptedException {        String importantInfo[] = {            "Mares eat oats",            "Does eat oats",            "Little lambs eat ivy",            "A kid will eat ivy too"        };         for (int i = 0;             i < importantInfo.length;             i++) {            //Pause for 4 seconds            Thread.sleep(4000);            //Print a message            System.out.println(importantInfo[i]);        }    }}

—————————————————————————————————————————————————————————————————————————————注意:在main的声明中致命了会抛出InterruptedException异常。这种异常在sleep出于激活态时被别的程序中断时会抛出的异常。由于在上面的示例程序中没有别的线程来进行中断,因此没有必要捕获这个异常。

—————————————————————————————————————————————————————————————————————————————

中断

中断就是一种指示:线程应该停止正在进行的事情。至于线程是如何响应这种中断指示完全是由程序员决定的,但是一般情况下都是终止该线程。这种使用方法也是在本节内容中所要强调的。

 

线程通过调用需要被中断的线程的interrupt方法来发送中断指示。为了能够使得中断机制正常工作,被中断的线程必须支持自身的中断。

 

支持中断

线程如何支持自身的中断了?这取决于它正在做的事情。如果线程中频繁调用能够抛出InterruptedException异常的方法,那么在捕获这种异常后,可以只是简单地退出线程。例如,在SleepMessages示例程序中,循环打印消息的代码段是在线程可运行对象的run方法中,那么可以按照如下方式对其进行修改,以便支持中断:

for (int i = 0; i < importantInfo.length; i++) {    // Pause for 4 seconds    try {        Thread.sleep(4000);    } catch (InterruptedException e) {        // We've been interrupted: no more messages.        return;    }    // Print a message    System.out.println(importantInfo[i]);}

许多抛出InterruptedException的方法,例如sleep方法的设计都是在接收到中断指示的时候立刻中断当前的操作并返回。

 

如果一个线程长时间运行而没有调用到可以抛出InterruptedException的异常,又该怎么样了?他必须周期性地调用Thread.interrupted来检查是否接收到中断指示。该方法在接收到中断指示的时候返回true。例如:

for (int i = 0; i < inputs.length; i++) {    heavyCrunch(inputs[i]);    if (Thread.interrupted()) {        // We've been interrupted: no more crunching.        return;    }}

在上面的这个简单示例程序中,我们仅仅是对中断指示进行了简单的检测,并在检测到该指示后退出线程。在更为复杂的应用程序中,抛出这种InterruptedException则显得根伟合理些。

if (Thread.interrupted()) {        throw new InterruptedException();    }

中断指示标记

中断机制的实现是通过内部的中断状态来完成的。调用Thread.interrupt会设置该状态。当一个线程通过调用静态的Thread.interrupted方法来检查另外一个线程的中断状态时,该状态会被清除掉。而使用非静态的isInterrupted方法来检查一个线程的中断状态则不会修改状态的值。

 

一般的做法是:任何通过抛出InterruptedException异常而退出的方法都会清除中断状态。然而,中断状态很快又被别的线程通过调用interrupt而再次设置也是有可能的。

 

线程的连接(join)

join方法将会使得一个线程等待另外一个线程执行结束。假如t是目前正在执行的线程线程对象:

t.join();

将会导致当前线程暂停,直到t的线程终止。join方法的重载版本使得程序员可以指定等待时长。然而,和sleep一样,这取决于操作系统,因此不能假定join方法的确可以等待那么长时间。

 

和sleep类似,join方法可以响应中断:通过抛出InterruptedException而退出。

SimpleThreads示例程序

下面的程序中用到了前面提到的部分概念。SimpleThreads程序中含有两个线程。第一个就是每个Java程序都有的main线程。在main线程中,通过Runnable对象创建了一个线程:MessageLoop,并等待期执行完毕。如果MessageLoop线程执行的时间过长,main线程就会中断它。

 

MessageLoop线程打印出一系列的信息。如果在完成信息打印之前被中断,该线程就会打印一个消息并退出。

public class SimpleThreads {    // Display a message, preceded by    // the name of the current thread    static void threadMessage(String message) {        String threadName =            Thread.currentThread().getName();        System.out.format("%s: %s%n",                          threadName,                          message);    }    private static class MessageLoop        implements Runnable {        public void run() {            String importantInfo[] = {                "Mares eat oats",                "Does eat oats",                "Little lambs eat ivy",                "A kid will eat ivy too"            };            try {                for (int i = 0;                     i < importantInfo.length;                     i++) {                    // Pause for 4 seconds                    Thread.sleep(4000);                    // Print a message                    threadMessage(importantInfo[i]);                }            } catch (InterruptedException e) {                threadMessage("I wasn't done!");            }        }    }    public static void main(String args[])        throws InterruptedException {        // Delay, in milliseconds before        // we interrupt MessageLoop        // thread (default one hour).        long patience = 1000 * 60 * 60;        // If command line argument        // present, gives patience        // in seconds.        if (args.length > 0) {            try {                patience = Long.parseLong(args[0]) * 1000;            } catch (NumberFormatException e) {                System.err.println("Argument must be an integer.");                System.exit(1);            }        }        threadMessage("Starting MessageLoop thread");        long startTime = System.currentTimeMillis();        Thread t = new Thread(new MessageLoop());        t.start();        threadMessage("Waiting for MessageLoop thread to finish");        // loop until MessageLoop        // thread exits        while (t.isAlive()) {            threadMessage("Still waiting...");            // Wait maximum of 1 second            // for MessageLoop thread            // to finish.            t.join(1000);            if (((System.currentTimeMillis() - startTime) > patience)                  && t.isAlive()) {                threadMessage("Tired of waiting!");                t.interrupt();                // Shouldn't be long now                // -- wait indefinitely                t.join();            }        }        threadMessage("Finally!");    }}