每个线程都跟一个Thread实例关联。有两种建立线程的基础策略。
+实例化一个Thread实例,程序异步新建一个线程执行任务(方便直接控制线程的创建和管理)
+传送任务给执行器(executor)执行(从应用的其他部分抽象化线程的管理)
本节使用方法一建立线程,执行器后面章节会介绍到。

1、定义并建立一个线程
定义一个线程需定义线程需提供线程执行的业务逻辑,有两种方式实现:
(1)实现Runable接口

public class HelloRunnable implements Runnable {

    public void run() {
        System.out.println("Hello from a thread!");
    }

    public static void main(String args[]) {
        (new Thread(new HelloRunnable())).start();
    }

}

(2)继承Thread类,实现方法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();
    }

}

上述的两种风格你应该怎么选择呢。第一种风格是比较常见的,因为实现runnable接口后还可以继承Thread类之外的类。第二种风格在一个简单应用中很容易使用,但受限于它是Thread的子孙类。本节主要使用风格一,它可以将线程(Thread)与任务(Runnable)分离。

风格一不仅比风格而灵活,而且比风格而更适用于高级API(线程池等)。

2、利用睡眠(Sleep)暂停一个线程
Thread.sleep可以暂停当前线程并指定暂停的时间。暂停时,线程的时间片将会让给系统中其他运行的线程或者进程使用。
sleep方法重载了两个版本,一个提供精确到毫秒,另一个则能精确到纳秒。
当然,睡眠时间不能保证精确的,这受限于系统设备。同时线程也可以在睡眠期间因线程中断(interrupts)而终止。

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时候对当前进程进行中断操作触发的。

3、线程中断(interrupts)
中断(interrupt)预示着线程应当停止正在做的事。由开发者来决定线程如何响应中断。
通过调用线程的interrupt方法来对线程发送中断命令。为了让中断机制能正确地工作,被中断线程应该能支持中断。
(1)支持中断
如何做能让一个线程支持中断呢?这取决于这个线程正在整啥犊子。如果它调用一个抛出InterruptedException,通常做法是获取(catch)异常后直接返回(return)。如上面的例子中,如果在runnable中执行,那应修改成下面:

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的又应该怎么处理呢?答案是周期性地调用InterruptedException,如果线程被中断则返回true。如下:

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

上面检测到中断直接退出,而通常在更复杂的程序中会手动抛出一个异常:

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

(2)中断标志(The Interrupt Status Flag)

中断机制(interrupt mechanism)通过线程内部的一个中断标志来判断线程中断状态。调用interrupt方法时候给这个标志设值。当线程通过调用Thread.interrupted来查看线程状态时候 该标志又会被清空。用非静态方法isInterrupted 查询,既不会清空标志,又能查到状态。
当抛出InterruptedException异常时候,也会清空标志。

4、加入(joins)
join函数可让一个线程等待另一个线程运行结束再继续运行。假如t是一个正在跑的线程,
调用

t.join()

将会使当前线程暂停知道t线程终止。join函数的重载函数能指定等待的时间长,当然,这个时间是不能保证精确的。
join、sleep 都会因为线程中断抛出异常。

5、程序示例

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!");
    }
}