线程的创建方式
线程的创建方式有四种,分别是继承Thread类、实现Runnable接口、实现callable接口、线程池,在这里我们只探讨前面三种方式。
1. 继承Thread类
首先是使用继承Thread类创建线程,我们需要继承Thread类还要重写run方法,然后在main方法中创建线程并调用start()方法来启动线程。
public class Demo {
public static class MyThread extends Thread {
@Override
public void run() {//重写run方法
System.out.println("MyThread");
}
}
public static void main(String[] args) {
Thread myThread = new MyThread();
myThread.start();//调用start方法
}
}
2. 实现callable接口
使用实现Runnable接口来创建线程,首先我们创建一个类来实现Runnable接口的run方法,然后也需要在main方法中调用start方法来启动线程。
public class Demo {
public static class MyThread implements Runnable {
@Override
public void run() {
System.out.println("MyThread");
}
}
public static void main(String[] args) {
new MyThread().start();
//因为Runnable接口是一个函数式接口,所以我们可以使用这种方式来创建线程。
new Thread(() -> {
System.out.println("线程运行");
}).start();
}
}
3. 实现callable接口
通常我们都是我们使用Runnable和Thread来创建一个新的线程。但是它们有一个弊端,就是run方法是没有返回值的。
但是有时候我们希望开启一个线程去执行一个任务,并且这个任务执行完成后有一个返回值。
这时候我们就可以使用实现callable接口的方式来创建线程。
我们可以使用ExecutorService可以使用submit方法来让一个Callable接口执行。
它会返回一个Future,我们后续的程序可以通过这个Future的get方法得到结果。
class Task implements Callable<Integer>{
@Override
public Integer call() throws Exception {
// 使得线程睡眠一秒
Thread.sleep(1000);
//返回一个2
return 2;
}
public static void main(String args[]){
ExecutorService executor = Executors.newCachedThreadPool();
Task task = new Task();
Future<Integer> result = executor.submit(task);
// 注意调用get方法会阻塞当前线程,直到得到结果。
System.out.println(result.get());
}
}
我们还可以使用一个类FutureTask配合实现callable接口的方式来创建线程。
class Task implements Callable<Integer>{
@Override
public Integer call() throws Exception {
// 使得线程睡眠一秒
Thread.sleep(1000);
return 2;
}
public static void main(String args[]){
ExecutorService executor = Executors.newCachedThreadPool();
FutureTask<Integer> futureTask = new FutureTask<>(new Task());
executor.submit(futureTask);
System.out.println(futureTask.get());
}
}
两种方式在使用上有一点区别。首先,调用submit方法是没有返回值的,我们不用接收返回值去调用get方法。
这里实际上是调用的submit(Runnable task)方法,而上面的Demo,调用的是submit(Callable
然后,这里是使用FutureTask直接取get取值,而上面的Demo是通过submit方法返回的Future去取值。
在很多高并发的环境下,有可能Callable和FutureTask会创建多次。FutureTask能够在高并发环境下确保任务只执行一次。
三种方式之间的比较
- 实现接口方式的好处
- 由于Java“单继承,多实现”的特性,Runnable接口使用起来比Thread更灵活。
- Runnable接口出现更符合面向对象,将线程单独进行对象的封装。
- Runnable接口出现,降低了线程对象和线程任务的耦合性。
- 如果使用线程时不需要使用Thread类的诸多方法,显然使用Runnable接口更为轻量。
- Runnable和Callable接口的比较
- Callable接口可以获取线程运行的信息以及中止线程,Runnable只能提供基本的线程运行工作,Callable的功能更丰富一些
- Callable的call()方法允许抛出异常,Runnable的run()方法则不允许
- 当使用FutureTask.get()方法时,主线程会阻塞,因为该方法返回的是该线程的运行结果,只有等到该线程结束才可以返回结果,而该方法写在主线程中,主线程会因为该方法等待线程结束而阻塞,直到返回出了运行结果,主程序才会继续运行,所以FutureTask.get()要在不需要并发的时候去调用