一、继承Thread
- 第一种写法:当继承Thread类时,你的类自动成为一个线程类。需要重写run方法,在其中编写线程要执行的代码。这是因为Thread类本身就实现了Runnable接口,并且它的start()方法会调用你重写的run()方法。
@Slf4j
public class ThreadTest extends Thread {
@Override
public void run() {
log.info("通过继承Thread类的方式实现创建线程......");
}
public static void main(String[] args) {
ThreadTest thread1 = new ThreadTest();
thread1.start();
}
}
- 第二种写法:使用匿名内部类的方式直接创建线程
@Slf4j
public class ThreadTest {
public static void main(String[] args) {
// 这里使用了Lambda的方式来简化写法
Thread t1 = new Thread(()->{ log.info("直接使用Thread创建线程.......");});
t1.start();
}
}
二、实现Runnable接口
- 第一种写法:
@Slf4j
public class ThreadTest implements Runnable {
@Override
public void run() {
log.info("通过实现Runnable接口的方式创建线程......");
}
public static void main(String[] args) {
ThreadTest t1 = new ThreadTest();
Thread thread1 = new Thread(t1);
thread1.start();
}
}
- 第二种写法:
@Slf4j
public class ThreadTest {
public static void main(String[] args) {
// 这里使用了Lambda的方式来简化写法
Runnable r1 = ()->{ log.info("通过实现Runnable接口的方式创建线程......");};
Thread t1 = new Thread(r1);
t1.start();
}
}
三、Callable和FutureTask接口
无论是Thread和Runnable创建的线程都无法获取线程的执行结果,如果想要获取执行结果就必须要通过共享变量或者线程通讯的方式才能实现。而jdk提供了Callable和Future,使得可以在线程执行结束之后获取执行结果。
Callable和Runnable是Java中用于定义线程任务的两种主要方式,而FutureTask则是用于将Callable或Runnable包装成可执行的任务并提供结果管理的一种机制。
1、Callable 接口
Callable是一个功能接口,本身并不创建线程,Callable的实例可以被提交给一个线程执行环境,比如ExecutorService,或者被包装进FutureTask中,然后由FutureTask被提交给线程执行。Callable与Runnable不同,Callable的任务可以返回一个泛型类型V的结果,且call()方法可以抛出异常。
虽然Callable定义了如何执行一个任务,但它本身并不执行任务。相反,当Callable被包装在FutureTask中,并且FutureTask被提交给一个ExecutorService或直接在一个Thread中运行时,Callable的任务才得以执行。
(1)关系结构
2、FutureTask 接口
FutureTask 是 Java 并发库中的一个重要工具类,它提供了一种机制来异步执行任务并在将来某个时刻获取任务的结果。它实现了 Runnable 和 Future 接口,因此可以在多线程环境中运行并且能够在未来某个时间点获取任务的结果。
FutureTask的缺点:一旦调用获取线程结果的方法,将会阻塞线程的执行。
@Slf4j
public class FutureTaskTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<Integer> ft1 = new FutureTask<>(new Callable<Integer>() {
@Override
public Integer call() throws InterruptedException {
log.info("使用FutureTask配合Callable的方式创建线程....");
TimeUnit.SECONDS.sleep(2);
return 100;
}
});
Thread t1=new Thread(ft1,"线程1");
t1.start();
// 当主线程执行到这一步时会等待ft1执行完成,只有当ft1线程执行结束之后才会往下执行
log.info("线程执行结果:"+ft1.get());
}
}
(2)关系结构
FutureTask实现了两个接口,Runnable和Future,所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。
(3)常用方法
1. public <T> get() throws InterruptedException, ExecutionException:此方法会阻塞当前线程,直到计算完成。一旦计算完成,它会返回计算的结果。如果计算过程中抛出了异常,get()方法将重新抛出一个ExecutionException,其中包含了导致任务失败的异常。
2. public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException:获取任务的执行结果,但在指定的超时时间内等待。如果任务在指定时间内完成,则返回结果;如果超时仍未完成,则抛出TimeoutException异常。
3. public boolean isCancelled():检查计算是否已被取消。如果在计算开始前或开始后调用了cancel()方法,并且取消请求被处理,那么此方法将返回
4. public boolean isDone():检查计算是否已经完成。如果计算已经完成执行(无论是否成功)、被取消,或者从未开始执行(例如,如果FutureTask被创建但从未调用run()或start()方法),此方法将返回true。计算完成或被取消,则返回true;否则返回false。
5. public boolean cancel(boolean mayInterruptIfRunning):尝试取消计算。如果计算尚未开始或已经开始但尚未完成,可以尝试取消它。如果mayInterruptIfRunning参数为true,并且计算正在执行中,那么此方法将尝试中断执行计算的线程。可以被取消,则返回true;否则返回false。
3、疑问解答
Callable 和 Runnable 的关系?
Callable和Runnable都可以用来封装线程要执行的任务,但是Callable支持返回结果和抛出异常,而Runnable则不支持。这意味着如果你需要从线程执行中获得结果,你应该使用Callable;如果只是执行一些操作,不关心结果,使用Runnable就足够了。Callable和FutureTask是什么关系?
当你使用Callable在call()方法里面定义好你的业务逻辑后,你需要一个机制来执行这个任务并获取结果。FutureTask正是这样一个机制,它将Callable任务包装起来,使其成为可以被线程执行的对象,并且提供了获取结果和管理任务状态的方法。