基础线程机制

Daemon

Java 中有两类线程:

  • 用户线程(User Thread):运行在前台的线程,比如主线程。
  • 守护线程(Daemon Thread):程序运行时在后台提供服务的线程,不属于程序中不可或缺的部分。

一旦所有的用户线程都退出了,虚拟机也就退出了,因此不要在守护线程中执行业务逻辑操作,因为随时都可能中断(甚至无法执行 finnally 中的语句)。

注意:在线程启动之前使用 setDaemon() 方法可以将一个线程设置为守护线程。

  1. public static void main(String[] args) {
  2. Thread thread = new Thread(new MyRunnable());
  3. thread.setDaemon(true);
  4. }

start() 和 run()

  • start()
    启动一个新线程,新线程会执行 run() 方法;
    start 不能被重复调用,如果重复调用,会抛 java.lang.IllegalException
  • run()
    和一个成员方法相同,可以被重复调用;
    单独调用 run() 不会启动新线程

sleep() 和 wait()

sleep() 和 wait() 的区别:

  • 持有者
    sleep() 是 Thread 的静态方法;
    wait() 是 Object 的成员方法,所以任何对象都可以调用 wait()
  • 锁是否会释放
    sleep() 会一直持有同步锁,继续占用 CPU 资源;
    wait() 会释放同步锁,使得其他线程可以使用同步块或者同步方法
  • 使用范围
    wait() 只能在同步代码块或同步方法中使用,否则会抛 java.lang.IllegalMonitorStateException
    sleep() 可在任何地方使用

yield() 和 wait()

Thread.yield() 声明了当前线程已经完成了生命周期中最重要的部分,可以切换给其它线程来执行。该方法只是对线程调度器的一个建议,而且也只是建议具有相同优先级的其它线程可以运行。

yield() 和 wait() 的区别:

  • yield() 是 Thread 的静态方法
    wait() 是 Object 的成员方法
  • yield() 让当前线程由“运行状态”进入“就绪状态”;
    wait() 让当前线程由“运行状态”进入“等待状态”
  • yield() 不会释放同步锁;
    wait() 会释放同步锁,使得其他线程可以使用同步块或者同步方法

notify() 和 notifyAll()

锁池:线程 A 已经拥有某个对象的锁,线程 B、C 想要调用这个对象的同步方法,此时线程 B、C 就会被阻塞,进入一个地方去等待锁的释放,这个地方就是该对象的锁池。

等待池:假设线程 A 调用了某个对象的 wait() 方法,线程 A 释放该对象的锁,同时线程 A 进入等待池,进入等待池的线程不会竞争该对象的锁。

notifyAll() 让所有处于等待池的线程进入锁池去竞争锁

中断线程

Sleep 来暂停执行

Thread.sleep 可以让当前线程执行暂停一个时间段,这样处理器时间就可以给其他线程使用。
sleep 有两种重载形式:一个是指定睡眠时间为毫秒,另外一个是指定睡眠时间为纳秒级。然而,这些睡眠时间不能保证是精确的,因为它们是通过由操作系统来提供的,并受其限制,因而不能假设 sleep 的睡眠时间是精确的。此外,睡眠周期也可以通过中断终止,我们将在后面的章节中看到。

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

请注意 main 声明抛出 InterruptedException。当 sleep 是激活的时候,若有另一个线程中断当前线程时,则 sleep 抛出异常。由于该应用程序还没有定义的另一个线程来引起的中断,所以考虑捕捉 InterruptedException。

中断(interrupt)

中断是表明一个线程,它应该停止它正在做和将要做的事。线程通过在 Thread 对象调用 interrupt 来实现线程的中断。为了中断机制能正常工作,被中断的线程必须支持自己的中断。

支持中断

如何实现线程支持自己的中断?这要看是它目前正在做什么。如果线程调用方法频繁抛出 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,被设计成在收到中断时立即取消他们当前的操作并返回。

若线程长时间没有调用方法抛出 InterruptedException 的话,那么它必须定期调用 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. }

在这个简单的例子中,代码简单地测试该中断,如果已接收到中断线程就退出。在更复杂的应用程序,它可能会更有意义抛出一个 InterruptedException:

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

注:interrupt()方法同样也不是一个正确的退出线程的方法,即便调用了该方法,该方法会抛出中断异常,但线程还是会继续运行。

中断状态标志

中断机制是使用被称为中断状态的内部标志实现的。调用 Thread.interrupt 可以设置该标志。当一个线程通过调用静态方法 Thread.interrupted 来检查中断,中断状态被清除。非静态 isInterrupted 方法,它是用于线程来查询另一个线程的中断状态,而不会改变中断状态标志。
按照惯例,任何方法因抛出一个 InterruptedException 而退出都会清除中断状态。当然,它可能因为另一个线程调用 interrupt 而让那个中断状态立即被重新设置回来。

join 方法

join 方法允许一个线程等待另一个完成。假设 t 是一个正在执行的 Thread 对象,那么

  1. t.join();

它会导致当前线程暂停执行直到 t 线程终止。join 允许程序员指定一个等待周期。与 sleep 一样,等待时间是依赖于操作系统的时间,同时不能假设 join 等待时间是精确的。
像 sleep 一样,join 并通过 InterruptedException 退出来响应中断。

终止线程

1. 退出标志

使用退出标志,使线程正常退出,也就是 run 方法完成后线程终止。
需要 while() 循环以某种特定的条件下退出,最直接的方法就是设计一个 boolean 类型的标志,并且通过设置这个标志来控制循环是否退出。
一般需要加上 volatile 来保证标志的可见性。

2. Thread.stop()

Thread.stop() 强制终止线程(不建议使用)。

3. interrupt() 中断线程

  • 线程处于阻塞状态,使用 interrupt() 则会抛出 InteruptedException 异常。
    (线程处于阻塞状态)
  • 使用 while(! isInterrupted()) {…} 来判断线程是否被中断,使用 interrupt() 则线程终止。
    (线程处于运行状态)

综合线程处于“阻塞状态”和“运行状态”的终止方式,通用的终止线程的形式如下:

  1. try{
  2. while(!isInteruppted()){ //isInteruppted() 保证只要中断标记为 true,就终止线程
  3. //...
  4. }
  5. }catch(InteruptedException e){
  6. //保证当发生 InteruptedException 时,线程被终止。
  7. }

举例如下:

  1. public class InterruptExample implements Runnable{
  2. @Override
  3. public void run() {
  4. int i = 0;
  5. try {
  6. //isInteruppted() 保证只要中断标记为 true,就终止线程
  7. while (!Thread.currentThread().isInterrupted()) {
  8. Thread.sleep(100);
  9. i++;
  10. System.out.println(Thread.currentThread().getName() + " (" +
  11. Thread.currentThread().getState() + ") loop " + i);
  12. }
  13. } catch (InterruptedException e) {
  14. // 保证当发生 InteruptedException 时,线程被终止。
  15. System.out.println(Thread.currentThread().getName() + " (" +
  16. Thread.currentThread().getState() + ") catch InterruptedException.");
  17. }
  18. }
  19. public static void main(String[] args) throws InterruptedException {
  20. Runnable interruptTask = new InterruptExample();
  21. Thread t1 = new Thread(interruptTask, "t1");
  22. System.out.println(t1.getName() +" ("+t1.getState()+") is new.");
  23. t1.start(); // 启动“线程t1”
  24. System.out.println(t1.getName() +" ("+t1.getState()+") is started.");
  25. // 主线程休眠300ms,然后主线程给t1发“中断”指令。
  26. Thread.sleep(300);
  27. t1.interrupt();
  28. System.out.println(t1.getName() +" ("+t1.getState()+") is interrupted.");
  29. // 主线程休眠300ms,然后查看t1的状态。
  30. Thread.sleep(300);
  31. System.out.println(t1.getName() +" ("+t1.getState()+") is interrupted now.");
  32. }
  33. }
  34. /* 结果如下:
  35. t1 (NEW) is new.
  36. t1 (RUNNABLE) is started.
  37. t1 (RUNNABLE) loop 1
  38. t1 (RUNNABLE) loop 2
  39. t1 (TIMED_WAITING) is interrupted.
  40. t1 (RUNNABLE) catch InterruptedException.
  41. t1 (TERMINATED) is interrupted now.
  42. */