Bootstrap

多线程系列(二) -Thread类使用详解

一、简介

在之前的文章中,我们简单的介绍了线程诞生的意义和基本概念,采用多线程的编程方式,能充分利用 CPU 资源,显著的提升程序的执行效率。

其中java.lang.Thread是 Java 实现多线程编程最核心的类,学习Thread类中的方法,是学习多线程的第一步。

下面我们就一起来看看,创建线程的几种方式以及Thread类中的常用方法。

二、创建线程的方式

在 JDK 1.8 版本中,创建线程总共有四种方式:

  • 继承 Thread 类
  • 实现 Runnable 接口
  • 使用 Callable 和 Future 创建线程
  • 使用 JDK 8 的 Lambda 创建线程
2.1、通过继承 Thread 创建线程

通过继承Thread类来创建线程是最简单的一种方法,继承类重写run()方法,然后通过线程对象实例去调用start()方法即可启动线程。

public class MyThread extends Thread{
   

    @Override
    public void run() {
   
        System.out.println(Thread.currentThread().getName() + "在运行!");
    }
}
MyThread thread = new MyThread();
thread.start();
2.2、通过实现 Runnable 接口创建线程

通过实现Runnable接口来创建线程也是最简单的一种方法,同时也是最常用的一种方式。

开发者只需要实现Runnable接口,然后通过一个Thread类来启动。

public class MyThread implements Runnable{
   

    @Override
    public void run() {
   
        System.out.println(Thread.currentThread().getName() + "在运行!");
    }
}
Thread thread = new Thread(new MyThread());
thread.start();
2.3、使用 Callable 和 Future 创建线程

相比通过实现Runnable接口来创建线程,使用CallableFuture组合来创建线程可以实现获取子线程执行结果,弥补了调用线程没有返回值的情况,可以看做是Runnable的一个补充,CallableFuture是 JDK1.5 版本中加入的。

public class MyThread implements Callable<String> {
   

    @Override
    public String call() throws Exception {
   
        System.out.println(Thread.currentThread().getName() + "在运行!");
        return Thread.currentThread().getName();
    }
}
Callable<String> callable = new MyThread();
FutureTask<String> ft = new FutureTask<>(callable);
new Thread(ft).start();
// 通过阻塞方式获取线程执行结果
System.out.println(ft.get());
2.4、使用 JDK 8 的 Lambda 创建线程

Lambda 表达式,是从 JDK1.8 版本开始加入的,可以看作成通过实现Runnable接口创建线程的一种简写。

new Thread(()-> System.out.println(Thread.currentThread().getName() + "在运行!")).start();
2.5、创建线程几种方式的对比

以上四种方式都可以创建线程,使用继承Thread类的方式创建线程时,编写简单,如果需要访问当前线程,无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。

采用实现RunnableCallable接口的方式创建线程时,线程类只是实现了 RunnableCallable接口,同时还可以继承其他类,最后通过Thread类来启动线程。它也是最常用的一种创建线程方式,通过接口方式来编程,可以实现代码更加统一。

其实通过继承Thread类创建线程的方式,本质上也可以看成实现了Runnable接口的一个实例,打开源码Thread,你会发现这一点。

public class Thread implements Runnable {
   
    
    //省略...
}

需要特别注意的地方是真正启动线程的是start()方法而不是run()方法,单独调用run()方法和调用普通的成员方法一样,不能启动线程

三、Thread 常用方法介绍

Thread 类常用的方法主要有三大块:

  • 构造方法
  • 实例方法
  • 静态方法
3.1、构造方法

在 JDK 中,Thread 类提供了如下几个常用的构造方法来创建线程。

方法 描述
Thread() 创建一个默认设置的线程实例,线程名称采用自增ID命名
Thread(Runnable target) 创建一个包含可执行对象的线程实例
Thread(Runnable target, String name) 创建一个包含可执行对象,指定名称的线程实例
Thread(String name) 创建一个指定名称的线程实例
Thread(ThreadGroup group, String name) 创建一个指定线程组,线程名称的线程实例
Thread(ThreadGroup group, Runnable target) 创建一个指定线程组,包含可执行对象的线程实例
Thread(ThreadGroup group, Runnable target, String name) 创建一个指定线程组,包含可执行对象,指定线程名称的线程实例
Thread(ThreadGroup group, Runnable target, String name, long stackSize) 创建一个指定线程组,包含可执行对象,指定名称以及堆栈大小的线程实例

其中Thread(Runnable target)构造方法最常见。

Thread thread = new Thread(new Runnable() {
   
    
    @Override
    public void run() {
   
        System.out.println(Thread.currentThread().getName());
    }
});
thread.start();

其次Thread(Runnable target, String name)构造方法,可以指定线程名称。

Thread thread = new Thread(new Runnable() {
   
    
    @Override
    public void run() {
   
        System.out.println(Thread.currentThread().getName());
    }
}, "thread-demo");
thread.start();

同时,还支持指定线程组来创建线程。

// 创建一个线程组实例
ThreadGroup tg = new ThreadGroup("线程组1");
// 创建一个线程实例
Thread thread = new Thread(tg,new Runnable() {
   
    
    @Override
    public void run() {
   
        System.out.println(Thread.currentThread().getThreadGroup().getName() + ":" + Thread.currentThread().getName());
    }
}, "thread-demo");
thread.start();

如果不显式指定线程组,JVM 会将创建的线程归到当前线程所属的线程组中。

关于线程组的相关知识,我们会在后期的系列文章中进行讲解。

3.2、实例方法

在 Java 中,实例方法只有实例对象才能调用,也就是new出来的对象或者反射出来的对象,类是无法直接调用的。

在 JDK 中,Thread 类提供了如下几个常用的实例方法来操作线程。

方法 描述
public void start() 启动线程
public void run() 线程进入可运行状态时,jvm 会调用该线程的 run 方法;单独调用 run 方法,不能启动线程
public final void setName(String name) 设置线程名称
public final void setPriority(int priority) 设置线程优先级,默认5,取值1-10
public final void setDaemon(boolean on) 设置线程为守护线程或用户线程,默认是用户线程
public final void join(long millisec) 挂起线程 xx 毫秒,参数可以不传
public void interrupt() 当线程受到阻塞时,调用此方法会抛出一个中断信号,让线程退出阻塞状态
public final boolean isAlive() 测试线程是否处于活动状态

下面我们依次来看看它们之间的用法。

3.2.1、start()

start()方法,简单的说就是启动线程,至于什么时候能运行,需要等待获取 CPU 时间片,然后调用线程对象的run()方法,产生一个异步执行的效果。

样例代码如下:

public class ThreadA extends Thread {
   

    @Override
    public void run() {
   
        for (int i = 0; i < 5; i++) {
   
            String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS").format(new Date());
            System.out.println(time + " 当前线程:" + Thread.currentThread().getName() + ",正在运行");
        }
    }
}
public class ThreadB extends Thread {
   

    @Override
    public void run() {
   
        for (int i = 0;
;