多线程的创建方式共有 4 种
- 继承Thread类的方式
- 实现Runnable接口的方式
- 实现Callable接口的方式
- 使用线程池创建
1. 使用继承Thread类的方式创建多线程
1.1 步骤
- 创建一个继承与Thread类的子类
- 重写Thread类的run() 方法 ,将想要用子线程执行的操作写在 run() 方法中
- 在主线程中创建继承Thread类的子类的对象
- 调用对象的 start() 方法启动子线程 (注意 这里使用的是 start() 方法 而不是 run() 方法)
1.2 注意点
- 不能直接在main方法中调用对象中的 run() 方法,因为如果调用对象的 run() 方法的话,实际是通过主线程执行的
- 不能让已经 start() 的子线程再次 start() ,这样会出现 IllegalThreadStateException() , 如果想再开一个子线程的话,需要创兴创建一个继承了 Thread类的子类的对象(下面例子中的 ThreadTest)
1.3 代码实例如下:
public class Main {
public static void main(String[] args) {
ThreadTest test = new ThreadTest();
//调用对象的 start() 方而不是run() 方法
test.start();
System.out.println("hello");
}
}
//ThreadTest 继承 Thread类
class ThreadTest extends Thread{
@Override
public void run() {
//想要实现的方法在run方法中执行
for (int i = 0; i < 100; i++) {
if(i % 2 == 0){
System.out.println(i);
}
}
}
}
2. 使用 实现Runnable的方式创建多线程
2.1 步骤:
- 创建一个实现类Runnable接口的类
- 实现类去实现Runnable中的抽象方法:run( )
- 创建实现类的对象
- 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
- 通过Thread类的对象调用start( )
2.2 代码实例如下
public class Main {
public static void main(String[] args) {
//2、创建一个实现类的对象
ThreadTest threadTest = new ThreadTest();
//3、将实现类的对象作为参数传递到Thread的构造器中
Thread t = new Thread(threadTest);
//4、调用thread对象的start方法
t.start();
//如果想要再执行一个线程的话:
Thread t2 = new Thread(threadTest);
t2.start();
}
}
//1、创建一个实现了Runnable接口
class ThreadTest implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i % 2 == 0){
//此时得到当前线程的名称应该使用Thread.currentThread()的方式
//因为是实现的Runnable接口,不是继承的Thread类
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
2.3 继承Thread类和实现Runnable接口的区别
都是在run( ) 方法中操作多线程,2种方式创建子线程的方式不同
3. 开发中为什么常使用使用 Runnable的方式创建多线程,而不是使用 继承Thread类的方式?
使用实现Runnable的方式没有类的单继承的局限性
4. 使用实现Callable的方式创建多线程
4.1 使用callable的方式创建多线程思路
1.提出假设
- 使用 继承Thread类,和实现Runnable接口的方式创建多线程,都和Thread类有关系,首先找一下Thread中,有没有一个可以传入Callable接口的方式创建多线程的构造方法
- 从java8 的官方api文档中,并没有找到传入Callable接口的方式创建多线程的实例 ( ThreadGroup也是一个类 ) 那么就只能类比一下实现Runnable接口的方式,因为Callable和Runnable都是接口
试想一下,有没有一个类 即和Runnable接口有关系,又和Callable接口有关系
那样的话就可以直接传入这个类就可以直接使用这个 Thread(Runnable target, String name) 的构造方法创建多线程了- 那么就有人问了 为什么试想的时候是试想类 而不是试想接口呢:
- Runnable接口和Callable接口没有任何关系,一个接口不可以同时继承2个接口 extend a, b( 当然 你要是说 c extend a, d extend c 然后找d 接口 虽然也说得通 但是 emmmmmmmm)
- 但是一个类可以同时实现2个接口
- 那么就有人问了 为什么试想的时候是试想类 而不是试想接口呢:
2.进入Runnable方法查找
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210131150758850.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM3Mzg1NTg1,size_16,color_FFFFFF,t_70)
那么就点到 RunnableFuture 进去看看 ( 当然,现在也可以点到类中,一个一个的查看和Callable有没有关系,如果实现的类比较多的话 就会比较难找,那我们不妨试试在子接口中查找,看看子接口的实现类 )
3.Runnable接口的子接口 RunnableFuture 中
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210131150822575.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM3Mzg1NTg1,size_16,color_FFFFFF,t_70)
4.RunnableFuture的实现类 FutureTask中(完成)
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210131150851338.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM3Mzg1NTg1,size_16,color_FFFFFF,t_70)
4.2 架构图FutureTask类和Runnable接口,Callable接口的关系如下
![在这里插入图片描述](https://img-blog.csdnimg.cn/202101311509305.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM3Mzg1NTg1,size_16,color_FFFFFF,t_70)
4.3 使用实现Callable的方式创建多线程实例
/**
*
* 使用Callable解决了Runnable的什么问题
* 最重要的一点,就是使用Runnable有返回值,可以根据返回值判断线程是否执行出错
*
* Callable和Runnable的区别
* 1.实现的方法不同
* Callable使用的是call方法
* Runnable使用的是run方法
* 2.是否抛出异常
* Callable可以抛出异常
* Runnable不能抛出异常
* 3.是否有返回值
* Callable有返回值
* Runnable没有返回值
*
* 使用new Thread(Runnable run , String name) 入手, 现在要找到Runnable和Callable的关系
* Runnable 是一个接口, 那么说明, Runnable的子接口也可以使用, 找到Runnable子接口的实现类
* 发现Runnable子接口的实现类可以传入一个Callable接口
* 根据多态的特性,可以使用new Thread() 的方式创建多线程
*
* 注意:get() 方法一般要放在最后一行
*
*/
public class ImplementCallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CallableSonClass callableSonClass = new CallableSonClass();
//得到Runnable和Callable有关联的类
FutureTask<Integer> futureTask = new FutureTask<>(callableSonClass);
//创建线程并启动, 使用的是new Thread(Runnable run, String name) 方法
new Thread(futureTask, "name a").start();
//如果使用<相同>的 futureTask启动线程, 线程不会再次执行call方法, 而是直接将上次执行完成的call方法的结果拿过来用
// new Thread(futureTask, "name b").start();
//get() 方法会导致线程等待, 直到当前线程执行完成后, 在会执行其他线程(相当于是在主线程中,把路给拦住了) 举个例子:高考先做难题
System.out.println(Thread.currentThread().getName() + "线程执行完成");
//get() 方法一般要放在最后一行
futureTask.get();
// System.out.println(Thread.currentThread().getName() + "线程执行完成");
}
}
class CallableSonClass implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("实现callAble方式创建多线程");
return 1;
}
}
4.4 FutureTask的缺点
- 使用FutureTask的get方法不管是否计算完成都会导致线程阻塞 等待租借出来再运行 ,get方法要在最后调用
4.5 FutureTask改进
方法1. get(long timeout, TimeUnit unit) 方法
使用FutureTask的 public V get(long timeout, TimeUnit unit) 方法,等待固定时长,如果在这个时长内程序还是没有运行完成,就会出现 juc.TimeOutException 异常
public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
FutureTask<Integer> futureTask = new FutureTask<>(() -> {
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
return 1024;
});
new Thread(futureTask, "aa").start();
//不见不散
// futureTask.get();
System.out.println("main线程继续执行");
//改进1, 过时不候
futureTask.get(2, TimeUnit.SECONDS);
}
可能出现:等待超时异常
![在这里插入图片描述](https://img-blog.csdnimg.cn/2021031011352950.png)
方法2. 使用CAS思想
缺点:轮询的方式会耗费无谓的CPU资源,而且也不见得能及时的得到计算结果
降低CPU资源解决方案:
- 在程序没有执行完成时 让线程等待一会 然后在执行
- 指定时间内没有执行完成,直接break
public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
FutureTask<Integer> futureTask = new FutureTask<>(() -> {
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
return 1024;
});
new Thread(futureTask, "aa").start();
System.out.println("main线程继续执行");
//改进2, CAS思想
while (true) {
if (futureTask.isDone()) {
//futureTask.isDone(),如果futureTask执行完成了, 就返回true
System.out.println(futureTask.get());
break;
} else {
//1.可以降低轮询的速度, 设置一个等待时间
//2.使用 System.currentTimeMillis() 判断执行了固定时长 例如5s,如果超过5s 则break
System.out.println("程序还没有执行完成,继续等待");
}
}
}
方法3. 使用CompletableFuture - 工作用
CompletableFuture实现了Future接口和 CompletionStage接口 架构图如下
CompletionStage是什么
CompletionStage:代表异步计算过程中的某一个阶段,一个阶段完成以后可能会触发另外一个阶段
一句话总结:餐馆点菜
详情:我现在点的一个菜在餐馆中没有材料,老板同事要求3个人把所有材料全部都买回来了,然后厨师下锅把这些材料依次添加进去,最后出锅的是一道已经做好的菜品 而不是材料
一个阶段的计算或执行可以使Function,Consumer或者Runnable。
比如stage.thenApply( x -> square(x) ). thenAccept( x -> System.out.print(x) ). thenRun( () -> System.out.print() )
一个阶段的执行可能是被单个阶段的完成触发,也可能是有多个阶段一起触发
CompletableFuture是什么
CompletableFuture 提供了 Future的扩展功能,可以简化异步编程的复杂性,提供了函数式编程的能力,可以通过回调的方式处理计算结果,也提供了转换和组合 CompletableFuture 的方法
CompletableFuture可能代表一个明确的Future,也有可能代表一个完成阶段 ( CompletionStage ), 他支持在计算完成以后出发一些函数或执行某些动作
CompletableFuture实现了 Future接口就相当于是具有了FutureTask的特性,然后在这个基础上有添加了一个新的特性:CompletionStage接口
5.CompletableFuture的4个核心静态方法 创建一个异步操作
4个静态方法中,不用执行 future.get() 方法 程序会自动创建一个线程运行 sync内部的内容,如果调用了 future.get() 方法 使用的是 FutureTask的特性,会将线程阻塞
如果没有指定线程池,直接使用默认的ForkJoinPool.commonPool() 作为线程池执行异步代码
如果指定了线程池,就使用自定义的线程池执行异步代码
无返回值 runAsync()
public static CompletableFuture<Void> runAsync(Runnable runnable)
使用默认的线程池异步开启一个线程进行调用
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
System.out.println(Thread.currentThread().getName() + "异步操作");
});
System.out.println("main线程执行");
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)
使用自己创建的线程池开启一个线程进行调用
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
1,
5,
10,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
System.out.println(Thread.currentThread().getName() + "异步操作");
}, threadPoolExecutor);
System.out.println("main线程执行");
threadPoolExecutor.shutdown();
有返回值 supplyAsync()
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
CompletableFuture<Integer> supplyAsync = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + "");
return 1;
});
System.out.println(supplyAsync.get());
System.out.println("main线程运行");
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
1,
5,
10,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
CompletableFuture<Integer> supplyAsync = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + "");
return 2;
}, threadPoolExecutor);
System.out.println(supplyAsync.get());
System.out.println("main线程运行");
threadPoolExecutor.shutdown();
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210310115804111.png)
thenApply方法
使用核心静态方法后,supplyAsync() 的返回值作为入参 使用这个入参执行一系列操作返回一个结果
whenComplete 、 exceptionally方法
当上一步操作完成了,判断上一步操作过程中是否出错,将上一步的操作结果、可能会出现的异常作为参数传入,
如果上一步正常执行,则第一个参数就是上一步执行的结果,异常信息就会显示为null
如果上一步异常执行,则第一个参数 为null, 异常信息 是这个异常信息, get() 的结果为 exceptionally() 方法中 return 的信息
举个例子:
//新开一个子线程干活, 有返回值
Integer integer = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + ":completableFuture2");
// int i = 10 / 0;
return 1024;
}).whenComplete((t, u) -> {
//如果正常执行,就会吧t打印出来
System.out.println(" --- t= " + t);
System.out.println(" --- u= " + u);
}).exceptionally(f -> {
//只有当程序出错的时候,才会进入这个方法,进入这个方法后, 也会让这个方法有一个异常信息
System.out.println("--exception:" + f.getMessage());
return 444;
}).get();
System.out.println(integer);
正常执行结果:
异常执行结果:
系统级别创建多线程原理
首先在Thread.java类中 调用的是本地方法,实际上调用的是 Thread.c ( c++ 语言),最终是由操作系统级别提供的
native关键字修饰的方法表示JVM通过jvm调用系统底层操作系统的函数方法