方式一、直接使用Thread类
无返回结果:继承Thread类,重写run();
public class UseThread extends Thread {
@Override
public void run() {
super.run();
// do my work;
System.out.println("I am extends Thread");
}
public static void main(String[] args) throws Exception {
UseThread useThread = new UseThread();
useThread.start();
//useThread.start();//重复启动报错
}
方式二、使用Runnable接口,配合Thread(受欢迎)
把【线程】和【任务】(要执行的代码) 分开。
【Thread】:代表线程。
【Runnable】:要运行的任务(线程要执行的代码)。
无返回结果: 实现Runnable接口,重写run();
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
实现Runnable接口
public class UseRunnable implements Runnable{
@Override
public void run() {
// do my work;
System.out.println("I am implements Runnable");
}
}
java8 lamda 实现Runnable(只有一个方法)接口的写法:
写法:(参数)-> {方法体}
Runnable run = () -> {
System.out.println("I am implements Runnable");
}
非阻塞调用:
public static void main(String[] args) throws Exception {
Thread useRunnable = new Thread(new UseRunnable());
useRunnable.start();
System.out.println("in main");
}
输出结果: 可以看到useRunnable线程的运行没有阻塞当前线程.
in main
I am implements Runnable
阻塞调用
public static void main(String[] args) throws Exception {
Thread useRunnable = new Thread(new useRunnable ());
useRunnable.start();
useRunnable.join();
System.out.println("in main");
}
输出结果: Join会阻塞当前线程,一直等待自定义线程执行完成,才返回。
I am implements Runnable
in main
方式三、实现Callable接口 或者叫 FutureTask配合Thread
JDK1.8中FutureTask实现了RunnableFuture,而RunnableFuture顾名思义就是Runnable接口和Future接口的结合体。因此,FutureTask对象可以作为Runnable对象来用。
FutureTask能够接收Callable类型的参数,用来处理有返回结果的情况。
有返回结果:
1. 实现Callable接口,重写call(),利用FutureTask包装Callable,并作为task传入Thread构造函数;
2. 利用线程池;
@FunctionalInterface
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
在Runnable的例子中,Runnable接口有一个很大的缺陷就是run方法没有返回值定义,主线程无法获取到线程执行的结果。这个时候就需要Callable接口。
Callable、Runnable区别:
1、Callable能接受一个泛型,然后在call方法中返回一个这个类型的值。而Runnable的run方法没有返回值;
2、Callable的call方法可以抛出异常,而Runnable的run方法不会抛出异常。
利用Callable接口创建并启动新线程的步骤:
① 定义CallableDemo 实现Callable接口;Class CallableDemo implements Callable<>
② 重写call(),将执行的代码写入;
③ 创建FutureTask的对象;FutureTask中定义了run(),run()内部调用了call(),并保存了call()的返回值;FutureTask<> futuretask = new FutureTask(CallableDemo());
④ 创建Thread的对象;Thread thread = new Thread(futuretask);//传入参数Runnable接口
⑤ 启动线程;thread.start();
⑥ 可通过FutureTask类的get()方法获得线程执行结束后的返回值,即call的返回值。
通过线程池来创建并启动新线程的步骤:
① 定义CallableDemo 实现Callable接口;Class CallableDemo implements Callable<>
② 重写call(),将执行的代码写入;
③ 定义线程池 :ThreadPoolExecutor executor
④ 利用submit()方法提交任务 :Future<?> future = executor.submit(new CallableDemo());
⑤ 利用FutureTask类get()方法获取返回值.
这里future申明为Future对象,但是它是由FutureTask实现的,也可以直接申明为FutureTask future:
示例代码如下:
public class CallableDemo implements Callable<CallableDto> {
public CallableDto call() throws Exception {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("in callable demo");
return new CallableDto(1);
}
}
class CallableDto {
private int id;
public CallableDto(int id) {
this.id = id;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
非阻塞调用
public static void main(String[] args) throws Exception {
ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(10);
Future<CallableDto> future = executor.submit(new CallableDemo());
System.out.println("in main");
}
输出结果,如下所示,新启动的线程没有阻塞当前线程
in main
in callable demo
阻塞调用,且拿到结果
public static void main(String[] args) throws Exception {
// 使用方式1:
ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(10);
Future<CallableDto> future = executor.submit(new CallableDemo());
CallableDto callableDto = future.get();
System.out.println("in main");
System.out.println("id from callable is " + callableDto.getId());
executor.shutDown();// 关闭线程池
System.out.println("------------------------------------------------------");
// 使用方式2:实现Callable接口需要用FutureTask进行包装,因为FutureTask实现了Runable接口和Future接口
CallableDemo callThread = new CallableDemo();
FutureTask<CallableDto> futureTask = new FutureTask<>(callThread);
new Thread(futureTask).start();
CallableDto callableDto2 = futureTask.get();
System.out.println("2:in main");
System.out.println("2:id from callable is " + callableDto2.getId());
}
}
输出结果:get方法首先会阻塞主线程,等待当前线程执行结束才返回,且返回线程的执行结果。
in callable demo
in main
id from callable is 1
方式四:CompletableFuture方式
CompletableFuture是jdk1.8引入的api,做了进一步的封装,用户线程无需实现Callable接口也能启动,且能够返回用户线程的执行结果。
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)
一个没有实现Callable的普通方法
public class CompletableFutureDemo {
public CompletableFutureDemoDto action() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("in CompletableFutureDemo ");
return new CompletableFutureDemoDto(1);
}
}
class CompletableFutureDemoDto {
private int id;
public CompletableFutureDemoDto(int id) {
this.id = id;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
非阻塞调用
public static void main(String[] args) throws Exception {
ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(10);
CompletableFuture<CompletableFutureDemoDto> future = CompletableFuture.supplyAsync(() ->
{
return new CompletableFutureDemo().action();
}, executor);
System.out.println("in main");
}
执行结果: 可以看到,主线程没有被阻塞
in main
in CompletableFutureDemo
阻塞调用且获取结果
public static void main(String[] args) throws Exception {
ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(10);
CompletableFuture<CompletableFutureDemoDto> future = CompletableFuture.supplyAsync(() ->
{
return new CompletableFutureDemo().action();
}, executor);
CompletableFutureDemoDto demoDto=future.join();
System.out.println("in main");
System.out.println("id from demoDto is " + demoDto.getId());
}
执行结果: 主线程一直被阻塞,一直等到用户线程返回
in CompletableFutureDemo
in main
id from demoDto is 1
总结:
线程的创建有四种方式:主要分为有返回和无返回,具体根据使用场景来选择。
1. 如果不需要返回且线程数量小,则建议采用实现Runnable接口,重写run()的方式;
2. 如果需要返回且线程数量小,则建议采用实现Callable接口,重写call(),利用FutureTask包装成一个Runnable,再作为参数传入Thread的构造方法的方式创建线程;
3. 如果线程数量较多,则建议采用线程池方式创建:execute提交任务实现无返回操作,submit提交任务实现有返回操作。