在 Java 中,有多种方式来实现多线程。下面通过实例来分别介绍继承 Thread 类、实现 Runnable 接口、Callable接口来创建线程。
1、继承 Thread 类
1.1类图
先来看下Thread的类图:
从类图中可以看出,Runnable
接口是一个函数式接口,里面只有一个抽象的run()
方法,Thread 类本质上就是实现了 Runnable 接口的一个实例。
启动线程的唯一方法就是通过 调用Thread类的 start()方法。start()方法是一个 native
方法,它会启动一个新线程,并执行 run()方法。
这种方式实现多线程很简单,通过自己的类直接 extend Thread
,并复写 run()方法,就可以启动新线程并执行自己定义的 run()方法。
对于线程的启动过程,会在下一章节进行详细讲解。
1.2 实例
public class ThreadDemo extends Thread {
@Override
public void run() {
System.out.println("运行线程-->"+Thread.currentThread().getName());
}
public static void main(String[] args) {
ThreadDemo threadDemo1 = new ThreadDemo();
ThreadDemo threadDemo2 = new ThreadDemo();
threadDemo1.start();
threadDemo2.start();
}
}
2、实现 Runnable 接口
如果自己的类已经 extends 另一个类,就无法直接 继承Thread,此时,可以实现一个 Runnable 接口。定义runnable接口的实现类,并重写该接口的run()方法。
实现方式如下:
public class RunnableDemo implements Runnable{
@Override
public void run() {
System.out.println("运行线程-->"+Thread.currentThread().getName());
}
public static void main(String[] args) {
Thread t1=new Thread(new RunnableDemo());
t1.start();
System.out.println("运行线程-->"+Thread.currentThread().getName());
}
}
3、实现 Callable 接口
3.1 类图
加入了Thread继承关系的类图:
通过类图可以清楚的看出,FutureTask
类实现了RunnableFuture
接口,RunnableFuture
又继承了Runnable
和Future
两个接口。
Callable对象不能直接作为Thread对象的target,因为Callable接口是 Java 5 新增的接口,不是Runnable接口的子接口。对于这个问题的解决方案,就引入 Future接口,此接口可以接受call() 的返回值,RunnableFuture接口是Future接口和Runnable接口的子接口,可以作为Thread对象的target 。并且, Future 接口提供了一个实现类:FutureTask 。
FutureTask实现了RunnableFuture接口,可以作为 Thread对象的target。
实现 Callable
接口可以通过 Future Task 包装器来创建 Thread 线程,有的时候,我们可能需要让一步执行的线程在执行完成以后,提供一个返回值给到当前的主线程,主线程需要依赖这个值进行后续的逻辑处理,那么这个时候,就需要用到带返回值的线程了。
简单理解一下就是:这是一个可以带返回值的线程。
3.2实例
public class CallableDemo implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("运行线程-->"+Thread.currentThread().getName());
return "SUCCESS";
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
CallableDemo2 callableDemo = new CallableDemo2();
FutureTask<String> futureTask = new FutureTask<>(callableDemo2);
new Thread(futureTask).start();
Thread.sleep(100);
System.out.println(futureTask.get());
}
}
4、Runnable和Callable区别
通过上述三种方式,其实可以归为两类:继承类和实现接口两种方式。相比继承, 接口实现可以更加灵活,不会受限于Java的单继承机制。并且通过实现接口的方式可以共享资源,适合多线程处理同一资源的情况。下面简单介绍一下两种接口类实现的区别:
-
1)Callable规定的方法是call(),Runnable规定的方法是run();
-
2)Callable的任务执行后可返回值,而Runnable的任务是不能返回值得;
-
3)call方法可以抛出异常,run方法不可以,因为run方法本身没有抛出异常,所以自定义的线程类在重写run的时候也无法抛出异常;
-
4)运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。
5、start()和run()的区别
- start()方法用来开启线程,但是线程开启后并没有立即执行,他需要获取cpu的执行权(分配的时间片)才可以执行;
- run()方法是由jvm创建完本地操作系统级线程后回调的方法,不可以手动调用(否则就是普通方法)。
总结:
本篇主要介绍了线程的三种创建方式,下一篇来介绍一下线程的启动过程,一个线程到底是怎么创建出来,又是怎么启动运行的呢,在下一篇你将会找到答案。