一、创建线程的三种方法
-
继承Thread,重写run()方法
步骤:
- 创建一个继承Thread的类,重写run方法
- 创建该类的对象,调用start方法,启动新线程执行run方法
代码示例:
** * 继承Thread,重写run方法,实现创建线程 */ public class Demo1 extends Thread{ @Override public void run() { System.out.println("new Thread"); } public static void main(String[] args) { Demo1 demo1 = new Demo1(); demo1.setName("demo1"); demo1.start(); } }
优点:简单易用,适合简单的线程任务。
缺点:由于Java只支持单继承,因此如果已经继承了其他类,则无法使用该方式创建线程。
-
实现Runnable接口,重写run()
步骤:
- 创建一个Runnable接口的实现类
- 在实现类中重写run方法
- 创建该实现类的对象
- 创建Thread类对象,构造方法中传入实现类对象
- 调用Thread类的start方法,启动新线程执行run方法
代码示例:
/**
* 实现Runnable接口,重写run方法创建线程:
*/
public class Demo2 implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "is running");
}
public static void main(String[] args) {
Thread thread1 = new Thread(new Demo2());
thread1.setName("Runnable Thread");
thread1.start();
// Lambda表达式创建
new Thread(new Runnable() {
@Override
public void run() {
// todo 执行业务
System.out.println("Lambda表达式 Runnable创建线程--2");
}
}).start();
}
}
优点:可以避免单继承的限制,适合多个线程共享一个资源的情况。
缺点:需要额外的类来实现Runnable接口,编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法
-
通过Callable和Future接口创建线程
步骤:
- 创建一下实现Callable接口的实现类,重写call方法
- 创建该实现类的对象,将其提供给FutureTask对象的构造参数
- 将FutureTask对象提供给Thread的构造函数以创建Thread对象
- 调用thread的start方法,启动子线程只执行call方法
- 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
代码示例:
/** * 通过Callable和Future接口创建线程 */ public class Demo3 implements Callable<String> { @Override public String call() throws Exception { // todo 执行业务 return "Callable创建线程--1"; } public static void main(String[] args) throws ExecutionException, InterruptedException { Demo3 demo3 = new Demo3(); FutureTask<String> futureTask = new FutureTask(demo3); new Thread(futureTask).start(); System.out.println(futureTask.get()); // Lambda表达式创建 FutureTask<String> futureTask2 = new FutureTask(() -> { // todo 执行业务 return "Lambda表达式 Callable创建线程--2"; }); new Thread(futureTask2).start(); System.out.println(futureTask2.get()); } }
优点:可以返回线程执行的结果,适合需要获取线程执行结果的情况。
缺点:相比前两种方式,稍微复杂一些。
二、Callable与Runnable区别
- 为了实现Runnable,需要实现不返回任何内容的run()方法,而对于Callable,需要实现在完成时返回结果的call()方法。
- call()方法允许异常向上抛出或者内部抓取,而run()的异常则要求内部处理
- 为实现Callable而必须重写call方法
- Callable不能直接替换Runnable,因为Thread类的构造方法根本没有Callable
三、Future接口
Future接口定义了操作异步任务执行一些方法,如获取异步任务的执行结果、取消任务的执行、判断任务是否被取消、判断任务执行是否完毕等。
拥有方法:
- public boolean cancel(boolean mayInterrupt):用于停止任务。如果尚未启动,它将停止任务。如果已启动,则仅在mayInterrupt为true时才会中断任务。
- public Object get()抛出InterruptedException,ExecutionException:用于获取任务的结果。如果任务完成,它将立即返回结果,否则将等待任务完成,然后返回结果。
- public boolean isDone():如果任务完成,则返回true,否则返回false
四、FutureTask
Java库具有具体的FutureTask类型,该类实现了Runnable和Future,并方便地将两种功能组合在一起。 可以通过为其构造函数提供Callable来创建FutureTask。然后,将FutureTask对象提供给Thread的构造函数以创建Thread对象。
作用:实现Callable接口创建线程的方式,当call()方法完成时,结果必须存储在主线程已知的对象中,以便主线程可以知道该线程返回的结果。因此,可以使用FutureTask对象。
一般使用:
在主线程中需要执行比较耗时的操作时,但又不想阻塞主线程时,可以把这些作业交给Future对象在后台完成
• 当主线程将来需要时,就可以通过Future对象获得后台作业的计算结果或者执行状态
一般FutureTask多用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。
• 仅在计算完成时才能检索结果;如果计算尚未完成,则阻塞 get 方法
• 一旦计算完成,就不能再重新开始或取消计算
• get方法而获取结果只有在计算完成时获取,否则会一直阻塞直到任务转入完成状态,然后会返回结果或者抛出异常
• get只计算一次,因此get方法放到最后,可以设置超时时间
五、总结
总的来说就三种创建方式
- 继承
Thread并重写run()方法; - 创建
Thread并执行Runnable任务; - 创建
Thread并执行Callable任务。
使用推荐:
- 单继承用第一种
- 如果已经继承了其他类而又不需要返回值,就用第二种
- 需要返回值,用第三种