JAVA中创建线程的三种方式的使用与区别
JAVA中通过继承Thread类、实现Runnable接口以及实现Callable接口配合Future接口实现创建多线程,三种方式各有优缺点,而第三种则具备更多的增强能力
创建线程
需要注意的是,本节没有讨论线程池的应用,仅针对单个线程的创建方式。
继承 Thread 类
/**
* 通过继承Thread类实现多线程
*/
public class MyThread extends Thread {
public MyThread() {
System.out.println("构造!调用线程名:"+Thread.currentThread().getName());
}
@Override
public void run() {
System.out.println("线程启动了!本线程名:"+getName());
for(int i=0;i<100;i++){
System.out.println(getName()+"++"+ i);
}
}
// 测试代码
public static void main(String[] args)
{
// 直接创建自定的继承类
new MyThread().start();
new MyThread().start();
}
}
实现 Runnable 接口
/**
* 通过实现Runnable接口创建线程类
*/
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("线程启动了!");
}
// 测试代码
public static void main(String[] args)
{
// 直接运行
new Thread(new MyRunnable()).start();
// 想获取当前线程的信息
Thread thread = new Thread(new MyRunnable());
thread.start();
System.out.println("线程启动了!线程的名字;"+thread.getName());
// 匿名内部类写法
new Thread(new Runnable(){
@Override
public void run() {
System.out.println("匿名内部类线程启动了!");
}
}).start();
// JDK1.8 lambda写法
new Thread(() -> {
System.out.println("lambda写法匿名内部类线程启动了");
}).start();
}
}
实现 Callable 接口配合 Future
/**
* 实现Callable接口配合Future创建线程
*/
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
Thread.sleep(5000);
System.out.println("子线程名:"+Thread.currentThread().getName());
return "返回值1";
}
// 测试代码
public static void main(String[] args)
{
MyCallable myCallable = new MyCallable();
// FutureTask能用来包装一个Callable或Runnable对象,因为它实现了Runnable接口,而且它能被传递到Executor进行执行
// RunnableFuture接口同时继承Future接口和Runnable接口,在执行run()方法后,可以通过Future访问执行结果,实现类是FutureTask
FutureTask<String> task = new FutureTask<String>(myCallable);
new Thread(task).start();
try {
//取消任务
//task.cancel(true);
//完成状态
//task.isDone();
//取消状态
//System.out.println("子线程的取消了吗:"+task.isCancelled());
System.out.println("子线程的返回值:"+task.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
优缺点
继承 Thread 类
优点:
- 编写简单
- run()方法内访问当前线程,使用 this 即可
缺点:
- 线程类不能再继承其他父类
实现 Runnable 或 Callable 接口
优点:
- 线程类只是实现了接口,还可以继承其他类
- 多个线程可以共享同一个 target 对象,适合多个相同线程来处理同一份资源
缺点:
- (不算缺点的缺点)访问当前线程,则必须使用 Thread.currentThread()方法
Runnable 或 Callable 区别
Callable 方案(JDK1.5)可以理解为是 Runnable 的增强版。
区别如下:
- Callable 的重写方法是 call(),Runnable 的重写方法是 run()
- Callable 的任务执行后可返回值,而 Runnable 的任务是不能返回值的
- Callable 的 call 方法可以抛出异常,Runnable 的 run()方法不可以
- Callable 任务可以拿到一个 Future 对象,表示异步计算的结果。
提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。
可以了解任务执行情况,可取消任务的执行,还可获取执行结果。
Future 接口提供的方法
boolean cancel(boolean mayInterruptIfRunning)
取消 Future 里关联的 Callable 任务
mayInterruptIfRunning 入参:是否允许在线程运行时中断
V get()
返回 Callable 任务里 call()方法的返回值
调用该方法将导致程序阻塞,必须等到子线程结束以后才会得到返回值
V get(long timeout, TimeUnit unit)
返回 Callable 任务里 call()方法的返回值
该方法让程序最多阻塞 timeout 和 unit 指定的时间
如果经过指定时间后,Callable 任务依然没有返回值,将会抛出 TimeoutException 异常
boolean isCancelled()
如果 Callable 任务正常完成前被取消,则返回 true
boolean isDone()
如果 Callable 任务已经完成, 则返回 true
