一、简介
在之前的文章中,我们简单的介绍了线程诞生的意义和基本概念,采用多线程的编程方式,能充分利用 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
接口来创建线程,使用Callable
和Future
组合来创建线程可以实现获取子线程执行结果,弥补了调用线程没有返回值的情况,可以看做是Runnable
的一个补充,Callable
和Future
是 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
即可获得当前线程。
采用实现Runnable
、Callable
接口的方式创建线程时,线程类只是实现了 Runnable
或Callable
接口,同时还可以继承其他类,最后通过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;