搜 索

Java并发编程丨Thread 类常见 API

  • 144阅读
  • 2022年01月20日
  • 0评论
首页 / 默认分类 / 正文

前言

了解线程的基本概念与几种生命周期状态后,进一步去了解 Java 为线程提供的 API。
点击打开官方文档,可以看到 java.lang.Thread 类的各个方法,本文只是介绍一些常见的。

currentThread() 方法

这是一个静态方法,返回对当前线程对象的引用,也就是说,返回代码段当前被哪一个线程调用。然后通过 getName() 方法得到线程的名称。

public class MethodTest {
    public static void main(String[] args) {
        // main
        System.out.println("当前的线程:" + Thread.currentThread().getName());
        MyTestThread thread = new MyTestThread();
        thread.start();
    }
}

class MyTestThread extends Thread {

    public MyTestThread() {
        System.out.println("调用构造方法的线程:" + Thread.currentThread().getName());
    }

    @Override
    public void run() {
        System.out.println("调用 run()方法的线程:" + Thread.currentThread().getName());
    }
}

获取线程信息的方法

除了 getName() 可以获取线程的名称,还有一些方法可以获取线程的信息,如下所示。

  • long getId():获取线程的唯一标识 id。
  • int getPriority():获取线程的优先级。
  • State getState():获取线程的状态,这个枚举类 State 就是我们所说的生命周期状态——NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED。
  • ThreadGroup getThreadGroup():获取线程的所属的线程组。
  • StackTraceElement[] getStackTrace():获取一个表示目标线程的堆栈跟踪元素数组。如果目标线程尚未启动或已经结束,返回的数组长度为 0。
  • ClassLoader getContextClassLoader():获取线程的 ClassLoader 上下文。

    isAlive() 方法

    这个方法用于判断当前线程是否存活,返回值类型为 boolean。

    public class MethodTest {
      public static void main(String[] args) {
          MyTestThread thread = new MyTestThread();
          // 尚未启动
          System.out.println("thread 线程是否存活:" + thread.isAlive());
          thread.start();
          // 已启动
          System.out.println("thread 线程是否存活:" + thread.isAlive());
          try {
              Thread.sleep(3000);
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
          // 等待运行完毕
          System.out.println("thread 线程是否存活:" + thread.isAlive());
      }
    }

    isDaemon() 方法

    Java 中有两种线程,一个是用户线程(非守护线程),也就是应用程序里的自定义线程;另一种就是守护线程(Daemon Thread),是指为其他线程服务的线程,例如,垃圾回收线程就是典型的守护线程。
    这个方法用于判断当前线程是否为守护线程,返回值类型为 boolean。只要在调用 start() 方法前,调用 setDaemon(true) 就可以把线程设置为守护线程。

    public class MethodTest {
      public static void main(String[] args) {
          MyTestThread thread = new MyTestThread();
          thread.setDaemon(true);
          thread.start();
          System.out.println("是否为守护线程:" + thread.isDaemon());
      }
    }

    sleep​(long millis) 方法

    这是一个静态方法,无返回值。作用是在指定的时间内(毫秒)内让当前的线程休眠,使得线程从 RUNNING 状态转变到 RUNNABLE 状态或者 BLOCKED 状态。
    如果输入的毫秒值为负数,将会抛出一个 IllegalArgumentException 异常;如果当前线程被中断,将会抛出一个 InterruptedException 异常,需要捕获。

    public class MethodTest {
      public static void main(String[] args) {
          MyTestThread thread = new MyTestThread();
          thread.start();
          try {
              // 休眠 1 秒
              Thread.sleep(1000);
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
          System.out.println("End");
      }
    }

    但是这种写法的可读性很差,需要手动把毫秒转换成秒和分。而位于 JUCjava.util.concurrent)包下的枚举类 TimeUnit 解决了这个问题,可以通过指定 NANOSECONDS(纳秒)、MICROSECONDS(微秒)、MILLISECONDS(毫秒)、SECONDS(秒)、MINUTES(分)、HOURS(时)、DAYS(天)作为休眠时长的单位。

    public class MethodTest {
      public static void main(String[] args) {
          MyTestThread thread = new MyTestThread();
          thread.start();
          try {
              // 休眠 1 秒
              // Thread.sleep(1000);
              TimeUnit.SECONDS.sleep(1);
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
          System.out.println("End");
      }
    }

    sleep() 方法除了 sleep​(long millis),还有一个重载方法 sleep​(long millis, int nanos),它的作用是在指定的毫秒数加指定的纳秒数内让当前的线程休眠。

    暂停线程的方法

    暂停线程不是停止线程,这意味着还可以恢复运行。

    suspend() 方法 与 resume() 方法

    suspend() 方法用于暂停线程,resume() 方法用于恢复线程。但官方文档将这两个方法都标记为 Deprecated,了解一下即可。

    public class MyThread6 extends Thread{
    
      private long  num = 0;
    
      public long getNum() {
          return num;
      }
    
      public void setNum(long num) {
          this.num = num;
      }
    
      @Override
      public void run() {
          while (true){
              num++;
          }
      }
    }
    
    public class ThreadSuspendTest {
      public static void main(String[] args) {
          try{
              MyThread6 myThread6 = new MyThread6();
              myThread6.start();
              TimeUnit.SECONDS.sleep(5);
              // 暂停线程。目前 'suspend()' 已经过时了
              myThread6.suspend();
              System.out.println("No.1 = " + System.currentTimeMillis() + ",num = " + myThread6.getNum());
              TimeUnit.SECONDS.sleep(5);
              // 测试是否真的暂停了
              System.out.println("No.1 = " + System.currentTimeMillis() + ",num = " + myThread6.getNum());
    
              // 恢复运行。目前 'resume()' 已经过时了
              myThread6.resume();
              TimeUnit.SECONDS.sleep(5);
    
              // 再次暂停
              myThread6.suspend();
              System.out.println("No.2 = " + System.currentTimeMillis() + ",num = " + myThread6.getNum());
              TimeUnit.SECONDS.sleep(5);
              System.out.println("No.2 = " + System.currentTimeMillis() + ",num = " + myThread6.getNum());
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
      }
    }

    如果使用 suspend() 方法与 resume() 方法使用不当,极易造成公共同步对象被独占、数据不完整、发生死锁等后果,所以现在使用 Object 类的 wait()notify()notifyAll() 方法来代替。

    停止线程的方法

    一般来说,线程在执行完之后会自动关闭,可是一些服务端的线程可能需要手动关闭,所以还是需要掌握如何停止线程的方法。

    stop() 方法

    stop() 方法确实可以停止线程,但这个方法不安全,也已被弃用作废。

interrupt() 方法

调用 interrupt() 方法仅仅是在当前线程中设置了一个停止的标志(把中断状态设置为 true),并不是真正的停止线程。

public class MyThread5 extends Thread{
    @Override
    public void run() {
        // 输出 50 W
        for (int i = 0; i < 500000; i++){
            System.out.println("i = " + (i + 1));
        }
    }
}

public class ThreadInterruptedTest {
    public static void main(String[] args) {
        try{
            MyThread5 myThread5 = new MyThread5();
            myThread5.start();
            TimeUnit.SECONDS.sleep(1);
            // 做中断标记
            myThread5.interrupt();
            // 输出 true 但是依然在运行
            System.out.println("===> 1.是否停止了? = " + myThread5.isInterrupted());
            System.out.println("===> 2.是否停止了? = " + myThread5.isInterrupted());
        } catch (InterruptedException e) {
            System.out.println("main catch");
            e.printStackTrace();
        }
    }
}

此外,还需要注意的是:

If this thread is blocked in an invocation of the wait(), wait(long), or wait(long, int) methods of the Object class, or of the join(), join(long), join(long, int), sleep(long), or sleep(long, int), methods of this class, then its interrupt status will be cleared and it will receive an InterruptedException.

简单翻译一下:如果该线程在调用 Object 类的 wait()、join()、sleep() 以及其重载方法时被阻塞,那么它的中断状态将被清除,并接收到 InterruptedException 异常。简而言之,如果在线程阻塞时调用中断方法 interrupt() 就会抛出异常,同时清理中断状态。

public class MyInterruptThread extends Thread {
    @Override
    public void run() {
        System.out.println("开始执行 run() 方法了...");
        try {
            TimeUnit.SECONDS.sleep(5);
            System.out.println("run() 方法执行完毕...");
        } catch (InterruptedException e) {
            System.out.println("线程在沉睡中被中断...进入了 catch 代码块,此时的中断状态:" + this.isInterrupted());
            e.printStackTrace();
        }
    }
}

public class InterruptAndSleepTest {
    public static void main(String[] args) {
        MyInterruptThread thread = new MyInterruptThread();
        thread.start();
        try {
            TimeUnit.SECONDS.sleep(1);
        }catch(InterruptedException e){
            e.printStackTrace();
        }
        thread.interrupt();
        System.out.println("【main】程序运行完毕");
    }
}

不管调用顺序,只要 interrupt() 和 sleep() 方法碰到一起就会出现异常。

interrupted() 方法

这是一个静态方法,用于判断当前线程是否已中断,返回值类型为 boolean。

The interrupted status of the thread is cleared by this method. In other words, if this method were to be called twice in succession, the second call would return false (unless the current thread were interrupted again, after the first call had cleared its interrupted status and before the second call had examined it).

简单翻译一下:此方法清除线程的中断状态。换句话说,如果这个方法被连续调用两次,第二次调用将返回 false(除非当前线程在第一次调用清除其中断状态之后,第二次调用检查之前再次中断)。

public class ThreadInterruptedTest2 {
    public static void main(String[] args) {
        // 中断当前线程:main 线程
        Thread.currentThread().interrupt();
        System.out.println("1.是否停止了? = " + Thread.interrupted());
        // interrupted() 方法具有清除中断状态的能力,前一次为 true,这次为 false
        System.out.println("2.是否停止了? = " + Thread.interrupted());
        System.out.println("End");
    }
}

isInterrupted() 方法

这不是静态方法,所以作用于调用这个方法的对象。用于判断线程 Thread 对象是否已经中断,返回值类型为 boolean。

The interrupted status of the thread is unaffected by this method.

简单翻译一下:线程的中断状态不受此方法的影响。也就是说,不具备清除状态标志的功能。

public class ThreadInterruptedTest3 {
    public static void main(String[] args) {
        // 中断当前线程:main 线程
        Thread.currentThread().interrupt();
        System.out.println("1.是否停止了? = " + Thread.currentThread().isInterrupted());
        // interrupted() 方法具有清除中断状态的能力,前一次为 true,这次为 false
        System.out.println("2.是否停止了? = " + Thread.currentThread().isInterrupted());
        System.out.println("End");
    }
}

使用退出标志

自定义一个退出标志,并对外提供一个改变退出标志的方法,线程每隔一段时间去判断下这个退出标志,如果是 false 则停止,否则继续运行。当然,这只是一个简单的举例说明,在业务开发中,肯定不能使用死循环来去判断是否需要停止线程的。

public class MyStopThread extends Thread {

    // 1.设置退出标志
    private volatile boolean flag = true;

    // 2.设置对外提供的停止方法
    public void stopThread() {
        this.flag = false;
    }

    @Override
    public void run() {
        int i = 0;
        while (flag) {
            System.out.println("【Test】子线程正在运行,现在 i = " + i++);
        }
    }
}

public class StopThreadTest {
    public static void main(String[] args) {
        MyStopThread test = new MyStopThread();
        // 启动线程
        new Thread(test).start();
        int time = 1000;
        while (time-- > 0){
            System.out.println("【main】线程正在倒计时:" + time);
            if(time == 996){
                // 停止线程
                test.stopThread();
                try{
                    // 这段时间内,没有 Test 线程输出
                    TimeUnit.SECONDS.sleep(2);
                }catch(InterruptedException e){
                    e.printStackTrace();
                }
                System.out.println("【Test】子线程应该停止了");
                System.out.println("【Test】子线程是否还存活:" + test.isAlive());
                break;
            }
        }
    }
}

使用异常法

这种方法使用 interrupt() 方法中断线程,在线程内部相应外部的中断信号。

class MyThread extends Thread {
    @Override
    public void run() {
        try {
            for (int i = 0; i < 500000; i++) {
                // 判断当前线程是否已经中断
                if (this.isInterrupted()){
                    System.out.println("当前线程已经是停止状态了,退出");
                    // 通过抛出异常停止
                    throw new InterruptedException();
                }
                System.out.println("当前 i = " + (i + 1));
            }
            System.out.println("for 循环结束了");
        }catch (InterruptedException e) {
            System.out.println("进入【MyThread】的 catch 代码块");
            e.printStackTrace();
        }
    }
}

public class StopThreadTest2 {
    public static void main(String[] args) {
        try {
            MyThread testThread = new MyThread();
            testThread.start();
            // 休眠
            TimeUnit.SECONDS.sleep(2);
            // 中断线程
            testThread.interrupt();
        }catch (InterruptedException e) {
            System.out.println("进入【main】的 catch 代码块");
            e.printStackTrace();
        }
        System.out.println("程序运行结束");
    }
}

使用 return 语句

将 interrupt() 方法与 return 语句结合使用,也能实现停止线程。

class MyThread2 extends Thread {
    @Override
    public void run() {
        int i = 0;
        while(true){
            // 不清除状态标志
            if (this.isInterrupted()){
                System.out.println("【MyThread2】线程停止运行了");
                // return
                return;
            }
            System.out.println("i = " + (i++));
        }
    }
}

public class StopThreadTest3 {
    public static void main(String[] args) {
        MyThread2 t = new MyThread2();
        t.start();
        try {
            TimeUnit.SECONDS.sleep(2);
        }catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 中断线程
        t.interrupt();
    }
}

虽然使用 “return;” 较 “抛异常” 法在代码结构上可以更加方便地实现线程的停止,不过还是建议使用 “抛异常” 法,因为在 catch 块中可以对异常的信息进行统一的处理。

yield() 方法

这是一个静态方法,无返回值。它的作用是放弃当前的 CPU 资源,让其他线程去占用 CPU 执行时间,恰如它的翻译——“让步、放弃”,但是放弃的时间不确定,有可能刚刚放弃,随后马上又获得 CPU 时间片。这个方法会使得线程从 RUNNING 状态转变到 RUNNABLE 状态。
以下两个线程执行相同的操作,观察结果可知:线程 A 将 CPU 资源让给其他线程,导致执行用时变长了。

class MyThread3 extends Thread {
    @Override
    public void run() {
        long beginTime = System.currentTimeMillis();
        System.out.println(Thread.currentThread().getName() + "线程开始执行");
        int count = 0;
        for (int i = 0; i < 500000; i++){
            // 线程A礼让其他线程
            if (Thread.currentThread().getName().equals("ThreadA")){
                Thread.yield();
            }
            count += (i + 1);
        }
        long endTime = System.currentTimeMillis();
        System.out.println(Thread.currentThread().getName() + "执行用时:" + (endTime - beginTime) + "毫秒...");
        System.out.println("线程停止执行,count = " + count);
    }
}

public class YieldThreadTest {
    public static void main(String[] args) {
        MyThread3 t = new MyThread3();
        new Thread(t, "ThreadA").start();
        new Thread(t, "ThreadB").start();
    }
}

join() 方法

此方法使用 final 修饰,无返回值,作用是可以使其他线程从 RUNNING 状态转换到 BLOCKED 状态,等待此线程死亡销毁。例如,当主线程需要用到子线程处理的数据,那么主线程需要等待子线程执行完毕再继续执行,类似于串联执行,这时就可以使用 join() 方法了。

public class MyJoinThread implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println("线程【VIP】,i = " + i);
        }
    }
}

public class JoinThreadTest {
    public static void main(String[] args) throws InterruptedException {
        MyJoinThread t = new MyJoinThread();
        Thread thread = new Thread(t);
        thread.start();
        for (int x = 0; x < 1000; x++) {
            if (x == 400) {
                // 到了 400,就先执行完 VIP
                thread.join();
            }
            System.out.println("线程【main】,x = " + x);
        }
    }
}

在使用 join() 方法的过程中,如果当前线程对象被中断,则当前线程会抛出 InterruptedException 异常。
此外,join() 方法还有两个重载方法:join​(long millis)join​(long millis, int nanos),与 sleep() 方法一样,其中的参数用于设置时间。例如,线程 A 执行 join(long millis),不管线程 A 是否执行完毕,一旦时间到了并且重新获得了锁,则当前线程会继续往下执行。

打 赏
  • 支付宝
  • 微信
Alipay
WeChatPay
评论区
暂无评论
avatar