1.创建线程的方式
区分线程和线程体的概念,线程体通俗点说就是任务。创建线程体的方式:像实现Runnable、Callable
接口、继承Thread
类、创建线程池等等,这些方式并没有真正创建出线程,严格来说,Java
就只有一种方式可以创建线程,那就是通过new Thread().start()
创建。
而所谓的Runnable、Callable……
对象,这仅仅只是线程体,也就是提供给线程执行的任务,并不属于真正的Java
线程,它们的执行,最终还是需要依赖于new Thread()
……
比如我们以new Runnable接口:
我们自定义了一个Runnable接口,通过主线程直接run,接着创建一个新线程并执行对应的任务。
注意:如果在这行的下面让主线程睡一会,再对这个runnable1任务调用run方法,JVM 可能会忽略第二次调用 run() 方法,尤其是在线程已经执行完毕的情况下。
其他的方法都差不多,本质都是只创建了任务(线程体),而不是线程。
线程是执行线程体的容器,线程体是一个可运行的任务。
2.如何理解线程安全与不安全
线程安全和不安全是在多线程环境下对于同一份数据的访问是否能够保证其正确性和一致性的描述。
线程安全指的是在多线程环境下,对于同一份数据,不管有多少个线程同时访问,都能保证这份数据的正确性和一致性。
线程不安全则表示在多线程环境下,对于同一份数据,多个线程同时访问时可能会导致数据混乱、错误或者丢失。
//TODO 一些场景下判断是否线程安全,如果不安全,有什么手段让其变的线程安全?
3.(线程)死锁
在多线程编程中,我们为了防止多线程竞争共享资源而导致数据错乱,都会在操作共享资源之前加上互斥锁,只有成功获得到锁的线程,才能操作共享资源,获取不到锁的线程就只能等待,直到锁被释放。
那么,当两个线程为了保护两个不同的共享资源而使用了两个互斥锁,那么这两个互斥锁应用不当的时候,可能会造成两个线程都在等待对方释放锁,在没有外力的作用下,这些线程会一直相互等待,就没办法继续运行,这种情况就是发生了死锁。
概括:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止
public class deadLock {
// 创建两把锁
static final Object resource1 = new Object();
static final Object resource2 = new Object();
public static void main(String[] args) {
new Thread(()->{
synchronized(resource1){
System.out.println(Thread.currentThread().getName()+"持有resource1锁...");
try {
sleep(1000);
synchronized(resource2){
System.out.println(Thread.currentThread().getName()+"持有resource2锁...");
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}finally{
System.out.println(Thread.currentThread().getName()+"释放锁");
}
}
}).start();
new Thread(()->{
synchronized(resource2){
System.out.println(Thread.currentThread().getName()+"持有resource2锁");
try {
sleep(1000);
synchronized(resource1){
System.out.println(Thread.currentThread().getName()+"持有resource1锁...");
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}finally{
System.out.println(Thread.currentThread().getName()+"释放锁");
}
}
}).start();
}
}
如何预防死锁? 资源有序分配法
线程 A 和 线程 B 获取资源(尝试获取锁)的顺序要一样,当线程 A 是先尝试获取资源 A,然后尝试获取资源 B 的时候,线程 B 同样也是先尝试获取资源 A,然后尝试获取资源 B。也就是说,线程 A 和 线程 B 总是以相同的顺序申请自己想要的资源。Future
4.Future接口
用于异步,主要用在一些需要执行耗时任务的场景,避免程序一直原地等待耗时任务执行完成,执行效率太低。具体来说是这样的:当我们执行某一耗时的任务时,可以将这个耗时任务交给一个子线程去异步执行,同时我们可以干点其他事情,不用傻傻等待耗时任务执行完成。等我们的事情干完后,我们再通过 Future类获取到耗时任务的执行结果。这样一来,程序的执行效率就明显提高了。(多路复用?)
我有一个耗时任务,交给Future去做,(期间我可以做其他事,我也可以去查看任务是否完成,是否被取消等等),将来我直接去找Future要任务的执行结果。
// V 代表了Future执行的任务返回值的类型
public interface Future<V> {
// 取消任务执行
// 成功取消返回 true,否则返回 false
boolean cancel(boolean mayInterruptIfRunning);
// 判断任务是否被取消
boolean isCancelled();
// 判断任务是否已经执行完成
boolean isDone();
// 获取任务执行结果
V get() throws InterruptedException, ExecutionException;
// 指定时间内没有返回计算结果就抛出 TimeOutException 异常
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutExceptio
}
FutureTask(实现)类
同时实现了Future接口和Runnable接口,本身是个Task,因此也是个线程体(任务),可以作为参数放到Thread的构造器里,或者通过线程池的submit方法(返回值是Future)来配合使用
submit和execute的区别? execute方法无返回值
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(10);
Future<String> submit = executorService.submit(new Callable1());
System.out.println(submit );//[Not completed]
String result = submit.get(); // 需要用get方法阻塞拿任务执行结果,
//这个submit只是个future引用,拿他没啥用
System.out.println(result); // 获取任务执行结果
System.out.println(submit );//[Completed normally]
// 休眠一秒钟,关闭线程池(不然这个线程不会被回收,服务不会关闭)
sleep(1000);
executorService.shutdown();
}
结论:线程池中的submit方法:对于一个任务,无论是实现Runnable接口、Callable接口还是一个FutureTask,底层都是封装为FutureTask,而FutureTask对象本身是实现Runnable接口的
FutureTask类本身的run方法,就是执行Runnable、Callable的实现类并获取返回结果的过程。
ExecutorService接口中submit方法归根结底还是要把你传入的对象封装成FutureTask对象,并通过FutureTask类的内部实现来获取结果的,无论是直接传入自己的Runnable、Callable实现类还是构建FutureTask对象传入,本质上都是通过FutureTask去实现,没有什么区别;
CompletableFuture(实现)类
提出场景:Future在实际使用过程中存在一些局限性比如不支持异步任务的编排组合(可以将多个异步任务串联起来,组成一个完整的链式调用)、获取计算结果的 get()
方法为阻塞调用。
提供了函数式编程的能力,可以通过回调的方式处理计算结果
创建异步对象
参数如果没带Executor,则用ForkJoinPool.commonPool() 作为它的线程池执行异步代码
supplyAsync 有返回值 runAsync无返回值
static CompletableFuture<Void> runAsync(Runnable runnable)
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)
创建回调
whenComplete可以处理正常和异常的计算结果,exceptionally处理异常情况。
BiConsumer<? super T,? super Throwable>可以定义处理业务
whenComplete 和 whenCompleteAsync 的区别:
前者当前线程继续执行,后者把任务提交给线程池
public CompletableFuture<T> whenComplete(BiConsumer<? super T,? super Throwable> action);
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action);
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action, Executor executor);
public CompletableFuture<T> exceptionally(Function<Throwable,? extends T> fn);
线程串行化
thenApply 方法:当一个线程依赖另一个线程时,获取上一个任务返回的结果,并返回当前任务的返回值。
thenAccept方法:消费处理结果。接收任务的处理结果,并消费处理,无返回结果。
thenRun方法:只要上面的任务执行完成,就开始执行thenRun,只是处理完任务后,执行 thenRun的后续操作
带有Async默认是异步执行的。这里所谓的异步指的是不在当前线程内执行。
public <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn, Executor executor)
public CompletionStage<Void> thenAccept(Consumer<? super T> action);
public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action);
public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action,Executor executor);
public CompletionStage<Void> thenRun(Runnable action);
public CompletionStage<Void> thenRunAsync(Runnable action);
public CompletionStage<Void> thenRunAsync(Runnable action,Executor executor);
多任务组合
前者所有任务完成,后者有一个完成即可。
public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs);
public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs);
5.原子类
参考: 异步编排(CompletableFuture异步调用)-CSDN博客https://blog.csdn.net/qq_33524158/article/details/107243344