前言

每个线程都与 Thread 类的一个实例相关联。有两种使用线程对象来创建并发应用程序的基本策略:

  • 为了直接控制线程的创建和管理,简单地初始化线程,应用程序每次需要启动一个异步任务。
  • 通过传递给应用程序任务给一个 executor,从而从应用程序的其他部分抽象出线程管理。

一、创建线程

程序创建一个Thread实例则必须提供在线程中运行的代码,有两种方式创建线程!

  • 提供Runnable 对象,Runnable 接口定义了单一的run方法,目的是包含线程中执行的代码。 ```java public class HelloRunnable implements Runnable { public void run() {
    1. System.out.println("Hello from a thread!");
    } public static void main(String args[]) {
    1. (new Thread(new HelloRunnable())).start();
    } }
  1. - 继承Thread类,Thread类自身实现了Runnable接口,尽管它的run方法没有什么作用,但是程序可以继承Thread类来提供自身的run方法的实现。
  2. ```java
  3. public class HelloThread extends Thread {
  4. public void run() {
  5. System.out.println("Hello from a thread!");
  6. }
  7. public static void main(String args[]) {
  8. (new HelloThread()).start();
  9. }
  10. }

注意到两种方式都调用了start()方法来启用新的线程。哪一种方式应该使用呢?显然,实现Runnable接口的方式更加普遍,因为Java是单继承,使用这种方式还可能继承一个非Thread类,可以将Runnable任务从执行任务的Thread对象中分离出来;继承Thread类的方式更易于在简单的程序中使用,但是限定了任务类必须是Thread类的后代了。
Thread类为线程管理定义了一些实用的方法,包括线程调用的一些提供相关信息或者影响线程状态的静态方法。其他方法从管理线程和线程对象所涉及的其他线程调用。


二、使用sleep方法暂停线程

使用sleep方法可以让线程在指定的时间段内暂停,这是一种使处理器时间可用于应用程序的其他线程或可能在计算机系统上运行的其他应用程序的有效方法。sleep方法还可以用于调整步调,以及等待另一个具有任务的线程,该任务被理解为具有时间要求。有两种重载的sleep方法可以使用,一个是单位为毫秒的,另外的一个是单位为纳秒的(十亿分之一秒)

  • **public static native void **sleep(**long **millis) **throws **InterruptedException;
  • **public static void **sleep(**long **millis, **int **nanos)

因为受制于底层系统提供的功能,这些睡眠时间都不能保证精准。此外,睡眠期间可以被中断,在任何情况下,您都不能假设调用sleep将在指定的时间段内暂停线程。
案例:线程睡眠4秒钟。

  1. public class SleepMessages {
  2. public static void main(String args[])
  3. throws InterruptedException {
  4. String importantInfo[] = {
  5. "Mares eat oats","Does eat oats","Little lambs eat ivy",
  6. "A kid will eat ivy too"
  7. };
  8. for (int i = 0;i < importantInfo.length;i++) {
  9. //Pause for 4 seconds
  10. Thread.sleep(4000);
  11. //Print a message
  12. System.out.println(importantInfo[i]);
  13. }
  14. }
  15. }

三、中断

中断操作是一种对线程应该停止正在做的事情,然后去做其他事情的指示。由程序员确切地决定线程如何响应中断,但线程终止是很常见的。一个线程通过调用interrupt发送一个中断到要中断的线程的线程对象上。为了使中断机制正常工作,被中断的线程必须支持自己的中断。

支持中断

线程如何支持自身的中断呢?这取决于当前它在做些什么事情。如果线程频繁地调用throw InterruptedException的方法,它仅在捕获该异常后从run方法返回。

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

很多抛出InterruptedException操作的方法,例如:sleep方法,被设计用来当收到中断的时候取消当前操作,然后立即返回。
如果线程运行长时间且没有调用抛出InterruptException异常,怎么办?然后它必须周期性地调用Thread.interrupted(中断),如果接收到中断将返回true。

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

中断状态标记

中断机制使用称为中断状态的内部标志来实现。调用Thread.interrupt设置这个标记。当一个线程通过调用静态方法Thread.interrupted来检查中断时,中断状态将被清除。一个线程查询另外的中断状态的时候使用非静态方法isInterrupted,不会改变中断状态标记。按照惯例,任何通过抛出InterruptedException退出的方法都会在抛出时清除中断状态。然而,总是有可能由另一个调用中断的线程立即再次设置中断状态。


Joins

join方法允许一个线程等待另外的线程完成。demoObject.join(); 如demoObject 是目前执行的线程对象,那么这行代码将会使得当前线程暂停执行直到demoObject的线程结束 。例如:在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。重载join方法允许程序员去指定等待时间。和sleep方法一样,join依赖于操作系统的计时,因此您不应该假设join将按照您指定的时间等待。
与sleep类似,join通过使用InterruptedException退出来响应中断。


简单的多线程例子

  1. public class SimpleThreads {
  2. // Display a message, preceded by
  3. // the name of the current thread
  4. static void threadMessage(String message) {
  5. String threadName = Thread.currentThread().getName();
  6. System.out.format("%s: %s%n", threadName, message);
  7. }
  8. private static class MessageLoop implements Runnable {
  9. public void run() {
  10. String importantInfo[] = { "Mares eat oats","Does eat oats","Little lambs eat ivy","A kid will eat ivy too" };
  11. try {
  12. for (int i = 0;i < importantInfo.length;i++) {
  13. // Pause for 4 seconds
  14. Thread.sleep(4000);
  15. // Print a message
  16. threadMessage(importantInfo[i]);
  17. }
  18. } catch (InterruptedException e) {
  19. threadMessage("I wasn't done!");
  20. }
  21. }
  22. }
  23. public static void main(String args[]) throws InterruptedException {
  24. // Delay, in milliseconds before
  25. // we interrupt MessageLoop
  26. // thread (default one hour).
  27. long patience = 1000 * 60 * 60;
  28. // If command line argument
  29. // present, gives patience
  30. // in seconds.
  31. if (args.length > 0) {
  32. try {
  33. patience = Long.parseLong(args[0]) * 1000;
  34. } catch (NumberFormatException e) {
  35. System.err.println("Argument must be an integer.");
  36. System.exit(1);
  37. }
  38. }
  39. threadMessage("Starting MessageLoop thread");
  40. long startTime = System.currentTimeMillis();
  41. Thread t = new Thread(new MessageLoop());
  42. t.start();
  43. threadMessage("Waiting for MessageLoop thread to finish");
  44. // loop until MessageLoop
  45. // thread exits
  46. while (t.isAlive()) {
  47. threadMessage("Still waiting...");
  48. // Wait maximum of 1 second
  49. // for MessageLoop thread
  50. // to finish.
  51. t.join(1000);
  52. if (((System.currentTimeMillis() - startTime) > patience) && t.isAlive()) {
  53. threadMessage("Tired of waiting!");
  54. t.interrupt();
  55. // Shouldn't be long now
  56. // -- wait indefinitely
  57. t.join();
  58. }
  59. }
  60. threadMessage("Finally!");
  61. }
  62. }