拥有多线程和拥有一百枚核弹没有区别,因为都是毁灭性的存在。——麦克阿瑟
在Java中,实现多线程主要有三种方式:继承Thread类、实现Runnable接口和实现Callable接口。
多线程的形式上实现方式主要有两种,一种是继承Thread类,一种是实现Runnable接口。本质上实现方式都是来实现线程任务,然后启动线程执行线程任务(这里的线程任务实际上就是run方法)。
万物皆可视为对象,线程也不例外。线程作为对象,具备可抽取的公共特性,这些特性可封装为类。通过使用类,我们可以实例化多个相同特性的对象。Thread类是JDK提供的一种简单方式来实现线程,通过继承Thread类并重写其run方法,我们可以在线程启动时执行自定义的run方法体内容。
在Spring Boot项目中,新创建一个TestThread测试线程类,并继承Thread,然后添加一个当前线程的名称,代码如下:
public TestThread() {
this.setName("测试线程");
}
接着重写run方法,来实现相关的业务逻辑,代码如下:
@Override
public void run() {
while (true) {
System.out.println("线程名:" + Thread.currentThread().getName());
try {
//线程休眠2秒
Thread.sleep(2000);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
然后在项目启动类中main方法中,启动该线程,在启动线程的时候,并不是调用线程类的run方法,而是调用了线程类的start方法。
public static void main(String[] args) {
SpringApplication.run(DemoThreadApplication.class, args);
TestThread thread = new TestThread();
//使用start,启动线程
thread.start();
while (true) {
System.out.println("线程名:" + Thread.currentThread().getName());
//线程休眠2秒
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
启动项目,然后在控制台会输出运行的结果:
线程名:main
线程名:测试线程
线程名:测试线程
线程名:main
线程名:测试线程
线程名:main
线程名:测试线程
线程名:main
主线程main和测试线程run方法交替执行,在控制台是随机输出的,原因就是cpu将时间片分给不同的线程,线程获得时间片后就执行任务,所以这些线程在交替的执行输出,导致输出呈现乱序的效果。由此可见,线程开启后不一定会立即执行,则是由cpu调度执行的。
为了避免Java单继承的限制,我们可以选择实现Runnable接口。通过实现Runnable接口的run()方法,我们可以将一个实现了该接口的对象传递给Thread类的构造方法来创建和启动线程。
Runnable接口的源代码如下:
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
使用Runnable创建线程的步骤如下:
创建线程任务,每隔2秒执行一次,代码如下:
public class TestRunnable implements Runnable{
@Override
public void run() {
while (true) {
System.out.println("TestRunnable线程名:" + Thread.currentThread().getName());
try {
//线程休眠2秒
Thread.sleep(2000);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
然后在启动类中创建线程,并将任务交付给线程进行处理,然后启动该线程,代码如下:
public static void main(String[] args) {
TestRunnable testRunnable = new TestRunnable();
//创建线程对象,通过线程对象来开启的线程
new Thread(testRunnable).start();
while (true) {
System.out.println("线程名:" + Thread.currentThread().getName());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
运行结果如下所示:
线程名:main
TestRunnable线程名:Thread-4
TestRunnable线程名:Thread-4
线程名:main
TestRunnable线程名:Thread-4
线程名:main
实现Runnable接口的输出结果和继承Thread类的结果是一样的,都是交替运行输出, 但是ava是单继承的,就比如我们一个类已经继承了其他的类,就不能使用继承Thread类来创建线程了。但是我们可以使用实现Runnable接口来创建线程。
Callable是Java中的一个接口,类似于Runnable,它允许定义一个可以有返回值的任务,并且这个任务可以并发执行。Callable接口和Runnable接口类似,但是它允许返回结果,并能抛出异常。Callable需要依赖FutureTask,用于接收运算结果。一个产生结果,一个拿到结果。FutureTask是Future接口的实现类,也可以用作闭锁。
在使用Callable接口时,需要注意以下几点:
使用Callable创建线程的步骤:
创建一个TestCallable类,然后实现Callable接口,并实现call方法,代码如下:
public class TestCallable implements Callable {
@Override
public Integer call() throws Exception {
System.out.println("开始执行Callable线程。。。");
// 睡1s
Thread.sleep(1000);
return 111;
}
}
然后在启动类中执行该线程。
public static void main(String[] args) throws ExecutionException, InterruptedException {
TestCallable testCallable = new TestCallable();
//执行Callable方式,需要FutureTask实现类的支持,
FutureTask<Integer> futureTask = new FutureTask(testCallable);
//启动线程
new Thread(futureTask).start();
System.out.println("接收线程运算的结果。。。");
Integer result = futureTask.get();
System.out.println("线程返回值:" + result);
}
运行结果如下所示:
接收线程运算的结果。。。
开始执行Callable线程。。。
线程返回值:111
综上所述,应该根据具体的需求和场景选择最合适的方式。如果只需要简单的启动线程并且不需要返回结果,可以选择继承Thread类的方式;如果需要传递参数并且不需要返回结果,可以选择实现Runnable接口的方式;如果需要返回结果并且能够处理异常,可以选择实现Callable接口的方式。
代码地址:https://github.com/dawandou/msy-code中的demo-thread项目。