Thread类API

  • Thread类API概述

    • Thread类中,除了run()start()方法经常使用外,线程在生命周期内可能还需要进行一些基本操作,如**sleep()****join()****interrupt()****isInterrupted()****interrupted()**以及从Object类继承的**wait()****notify()****notifyAll()**等,这些操作会成为线程间一种通信和交互的方式

      线程间通信方式还有JUC包中提供的方法,即await()signal()signalAll()park()unpark(Thread thread)

    • 对于sleep()join()wait(),需要注意的是什么时候抛出InterruptedException异常以及如何处理该异常,这些都与中断有关,

1 构造方法

  • **Thread()**
  • **Thread(String name)**

返回一个线程实例,该实例的run()方法什么也不做

  • **Thread(Runnable target)**
  • **Thread(Runnable target, String name)**

返回一个线程实例,该实例的run()方法将调用参数target的run()方法

  • 可以传入一个字符串作为线程的名称,否则为默认名称“Thread-n”
  • Thread类run()方法源码,其中target为Thread类的成员变量private Runnable target ```java private Runnable target;

@Override public void run() { if (target != null) { target.run(); } }

  1. - Thread类实现了Runnable接口,也就意味着**构造函数**`**Thread(Runnable target)**`**不仅可以传入Runnable接口的对象,还可以传入Thread类的对象(多态)**,因此可以将一个Thread实例中的`run()`方法交由其他的Thread实例进行调用。
  2. <a name="Q4kI8"></a>
  3. ### 2 静态方法
  4. - **静态方法都是作用于当前线程的**
  5. - `**Thread Thread.currentThread()**`
  6. 返回当前线程实例
  7. - 使用继承Thread类的方式创建线程时,在线程内不需要使用该方法,直接使用this指针即可获取当前线程
  8. - `**void Thread.sleep(long millis) throws InterruptedException**`
  9. `sleep()`方法会让当前线程从**RUNNABLE**转为**TIMED_WAITING**,到达指定时间后自动恢复**RUNNABLE**状态
  10. - 该方法可以接收一个参数毫秒millis,也可以接收两个参数毫秒millis和纳秒nanos
  11. - **如果当前线程持有某个锁,即使进入睡眠状态,也不会放开锁**
  12. - **线程休眠期间可以被中断**,中断将会抛出`InterruptedException`异常
  13. - 在开发时常常需要模拟耗时任务处理过程(即`while(true){ }`循环),此时需要在循环体内加`sleep()`方法,避免CPU占用100%
  14. - 建议使用**TimeUnit类**的方法来代替`Thread.sleep()`方法,前者可读性更好
  15. - `**void Thread.yield()**`
  16. `yield()`方法会让当前线程从**RUNNING**转为**READY**,让出时间片给其他和当前进程拥有相同优先级的线程执行。
  17. - **该方法并不改变线程状态**,而是放弃当前CPU时间片,**重新参与时间片竞争,线程仍然是RUNNABLE状态**
  18. - 如果没有正在等待的线程,或是等待线程的优先级不高,当前线程可能继续运行,即`**yield()**`**方法无法确保暂停当前线程。**
  19. `**yield()**`**和**`**sleep()**`**的区别**
  20. - 两个方法同样都是令当前线程交出处理器资源,不同的是,`**sleep()**`**方法令当前线程放弃CPU后,可使任意优先级的线程获得执行的机会;**`**yield()**`**则只能使同优先级的线程有执行的机会**。
  21. - `**boolean Thread.interrupted()**`
  22. 返回当前线程的中断标志位,**该方法会清除中断标记**
  23. <a name="DBax0"></a>
  24. ### 3 实例方法
  25. - **实例方法都是作用于Thread实例对象的**
  26. - `**synchronized void thread.start()**`
  27. `start()`用来启动一个线程
  28. - 调用该方法后,系统会开启一个新的线程来执行线程实例的`run()`方法,在这个过程中,会为相应的线程分配需要的资源
  29. - **每个线程实例只能调用**`**start()**`**方法一次**
  30. - start()方法只是让线程进入就绪状态,并不一定立刻开始执行
  31. - `**void thread.run()**`
  32. `run()`方法是一个线程所要执行的具体任务,新线程启动后会调用该方法
  33. - `run()`方法是不需要用户来调用的,当一个线程启动并获得了CPU时间片,便进入`run()`方法体去执行具体的任务
  34. - `**synchronized void thread.join() throws InterruptedException**`
  35. - `**synchronized void thread.join(long millis) throws InterruptedException**`
  36. **join()方法是线程间协作的一种方式、一种线程同步的方法,使得当前线程将等待thread运行结束才能继续运行**
  37. - 例如线程threadA持有线程threadB的实例,如果线程threadA执行了`threadB.join()`方法,那么线程threadA会在线程threadB终止后继续执行。
  38. - `thread.join()`方法需要在`thread.start()`方法之后,如thread已经执行完成,则当前线程不会等待
  39. - **在threadA等待的过程中,可以被中断**,中断将抛出`InterruptedException`异常。
  40. - **可以向**`**join()**`**方法传入超时时间参数**,表示等待线程threadB终止的上限时间,超过时间后threadA将停止等待,继续执行。等待时间0表示threadA一直等待,直至threadB终止,与不传入参数的效果相同。
  41. - 根据是否传入等待时间,threadA将进入**TIMED_WAITING**或**WAITING**
  42. **实例**<br />主线程开启了4个子线程,每个子线程都等待前一个线程终止后继续执行
  43. ```java
  44. public class MyClass {
  45. public static void main(String[] args) {
  46. Thread previousThread = Thread.currentThread();
  47. for (int i = 1; i <= 4; i++) {
  48. Thread curThread = new JoinThread(previousThread);
  49. curThread.start();
  50. previousThread = curThread;
  51. }
  52. }
  53. }
  54. class JoinThread extends Thread {
  55. private Thread thread;
  56. public JoinThread(Thread thread) {
  57. this.thread = thread;
  58. }
  59. @Override
  60. public void run() {
  61. try {
  62. System.out.println(getName() + " is running.");
  63. thread.join();
  64. System.out.println(getName() + " out: " + thread.getName() + " terminated.");
  65. } catch (InterruptedException e) {
  66. e.printStackTrace();
  67. }
  68. }
  69. }
  • 上述代码输出如下,可以看到所有线程开始执行**run()**的顺序是随机的,但是终止的时间是有顺序的
    1. Thread-2 is running.
    2. Thread-1 is running.
    3. Thread-0 is running.
    4. Thread-3 is running.
    5. Thread-0 out: main terminated.
    6. Thread-1 out: Thread-0 terminated.
    7. Thread-2 out: Thread-1 terminated.
    8. Thread-3 out: Thread-2 terminated.
  • **void thread.setDaemon(boolean on)**

当on为true时,将thread标记为守护线程,否则标记为用户线程

  1. public final void setDaemon(boolean on) {
  2. checkAccess();
  3. if (isAlive()) {
  4. throw new IllegalThreadStateException();
  5. }
  6. daemon = on;
  7. }
  • 该方法必须在调用**start()**方法之前调用,否则会抛出IllegalThreadStateException异常
  • 如果checkAccess()确定调用该方法的线程不能修改此线程,则抛出SecurityException异常
  • **void thread.interrupt()**

中断线程thread,thread被中断后会有以下三种情况

  1. 如果thread在**sleep()****wait()****join()****await()**方法中thread会被唤醒并抛出**InterruptedException**异常还会清除中断标记
  2. 如果thread正在运行中,则会设置中断标记,由thread自己决定何时处理中断
  3. 如果thread**LockSupport.park()**方法中,则线程会被唤醒,但不会抛出异常,也不清除中断标记

**thread.interrupt()**方法可以实现两阶段终止模式,即优雅的给thread一个料理后事的机会

  • 使用thread.stop()(该方法已废弃,被thread.interrupt()替代)会直接杀死thread,如果thread锁住了共享资源,那么当它被杀死后就没有机会释放锁,其它线程将永远无法获取锁
  • **boolean thread.isInterrupted()**

返回thread的中断标志位,如果设置中断标记,则返回true,否则返回false

  • 该方法不会清除thread的中断标志位,**Thread.interrupted()**方法会清除当前线程中断标志位
  • **void thread.setName(String name)**
  • **String thread.getName()**

设置/获取thread的名字

  • 线程的名称并非必须显式提供的,系统会为每个线程自动生成一个名称,形如Thread-n,其中n是整数,表示线程的序号。主线程的名称是main
  • 建议显式赋予每个线程名字,方便调试、排错等,否则无法直观知道某个线程的用途
  • **void thread.setPriority(int priority)**
  • **int thread.getPriority()**

设置/获取thread的优先级

  • 优先级是1~10之间的整数
  • **State thread.getState()**

获取thread的状态
State是Enum类型,值包括NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED

  • **boolean thread.isAlive()**

返回thread是否存活(是否运行完成)

Object类API

实例方法

  • **final void obj.wait() throws InterruptedException**
  • **final native void obj.wait(long timeoutMillis) throws InterruptedException**

让持有obj的线程进入WAITING或TIMED_WAITING状态

  • 必须获得锁对象obj才可以调用该方法
  • 调用wait()方法的线程可以被中断,但是一般不采取这种方式唤醒线程
  • **Thread.sleep()****obj.wait()**的区别
    • sleep()方法令线程进入TIMED_WAITING状态,而wait()方法则令线程进入WAITING状态,向wait()方法传入指定参数后,也可以进入TIMED_WAITING状态
    • **wait()**方法必须要在同步方法或者同步块中调用,也就是说必须已经获得对象锁

**sleep()**方法则可以在任何地方使用。

  • **wait()**方法会使线程释放已经占有的对象锁,使该线程进入等待池中。

**sleep()**方法只是会让出CPU,并不会释放掉对象锁

  • **sleep()**方法在休眠时间达到后,如果再次获得CPU时间片就会继续执行

**wait()**方法必须等待**Object.notify()****Object.notifyAll()**通知后,才会离开等待池,并且再次获得CPU时间片才会继续执行。当给**wait()**方法传入参数时,也可以在睡眠时间达到后,进入RUNNABLE状态。

  • **final native void obj.notify()**
  • **final native void obj.notifyAll()**

在与obj关联的Monitor的WaitSet中等待线程中随机选择一个唤醒或全部唤醒

  • 必须获得锁对象obj才可以调用该方法
  • notify() / notifyAll()方法很可能导致虚假唤醒,如某个线程的条件满足了可以调用**notify()**唤醒WaitSet中的线程,但此时唤醒的却是等待另一个条件的线程

常使用while循环判断条件是否满足来防止虚假唤醒,即如果被唤醒后条件仍然不成立,则继续调用**wait()**方法进入等待

  • **thread.join()**方法与**wait()**方法

**join()**方法内部实际上是由**wait()**方法实现的,join()源码如下

  1. public final synchronized void join(long millis)
  2. throws InterruptedException {
  3. long base = System.currentTimeMillis();
  4. long now = 0;
  5. if (millis < 0) {
  6. throw new IllegalArgumentException("timeout value is negative");
  7. }
  8. if (millis == 0) {
  9. //如t1执行t2.join(),则t1获取锁对象t2,因此这里的this.wait(0)是让t1阻塞的
  10. //t1将进入与t2关联的Monitor的WaitSet,当t2运行结束后t1将被唤醒
  11. while (isAlive()) {
  12. wait(0);
  13. }
  14. } else {
  15. //防止虚假唤醒导致提前结束等待
  16. while (isAlive()) {
  17. long delay = millis - now;
  18. if (delay <= 0) {
  19. break;
  20. }
  21. wait(delay);
  22. now = System.currentTimeMillis() - base;
  23. }
  24. }
  25. }
  • 如果某个线程是锁对象,那么该线程在执行结束后会自动调用this.notifyAll()来唤醒等待的线程