JUC基础知识(二)

一:Callable、Future接口

下面截取JDK源码中 Thread类的注释部分。
在这里插入图片描述

目前我们学习了有两种创建线程的方法
一种是通过继承 Thread 类,另一种是通过实现 Runnable 接口 创建线程。(这两种方式是最基本的,其他的Callable接口和线程池其实还是基于这两种方式实现的

但是,Runnable 缺少的一项功能是,当线程终止时(即 run() 完成时),我们无法使线程返回结果。为了支持此功能,Java 中提供了 Callable 接口。

Callable 接口源码:
在这里插入图片描述
Callable接口,只是用于单纯地去获得返回值,但是什么时候去获取,或者查看线程运行的状态,就需要借助Future

Future的源码如下:

public interface Future<V> {
 
    //取消任务的执行,任务已经完成或者已经被取消时,调用此方法,会返回false
    boolean cancel(boolean mayInterruptIfRunning);
 
    //判断任务是否被取消
    boolean isCancelled();
 
    //判断任务是否完成(正常完成、异常以及被取消,都将返回true)
    boolean isDone();
 
    //阻塞地获取任务执行的结果
    V get() throws InterruptedException, ExecutionException;
 
    //在一定时间内,阻塞地获取任务执行的结果
    V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
}

可以看得出来,Future提供了取消任务、获取任务执行的状态与获取结果的能力。

类图关系:
在这里插入图片描述
可以看出来 FutureTask中的构造方法可以传递Callable,而且Runnable接口有实现类FutureTask,所以说FutureTask是把Callable和Runnable接口联系在了一起。所以说其实使用Callable接口创建线程其实本质上还是实现Runnable,只不过使用FutureTask联系这两者能够方便的获取到返回值。这也就照应了上面说的创建线程有两种方式。1:继承Thread类。2:实现Runnable接口。

使用Callable接口创建线程:

List<Integer> list = new ArrayList<>();
//Lambda表达式
FutureTask<Object> futureTask = new FutureTask<>(()->{
    list.add(1);
    list.add(2);
    list.add(3);
    return list;
});

//创建一个线程
new Thread(futureTask).start();
System.out.println(futureTask.get());

二:Java中的线程池

1、实现机制

主线程使用ececute() 方法,向线程池中提交任务

①首先会先判断当前线程数是否超过了core Pool中规定的最大线程数,如果未超出,就会直接在core Pool中创建一个新的线程。②如果已经超出,创建线程的任务会进入阻塞队列BlockingQueue中进行等待。③如果core Pool和BlockingQueue都已经满了,则就会在max Pool中创建新的线程。④如果连max Pool中都满了,那么线程池就会采取一种饱和策略来解决问题(饱和策略在下面线程池的创建中会有介绍)。
在这里插入图片描述

2、线程池的创建

new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, milliseconds, runnableTaskQueue, handler);

创建一个线程池时需要输入几个参数,如下:(七个核心参数)

1)corePoolSize(线程池的基本大小)。

2)maximumPoolSize(线程池最大数量)。

3)keepAliveTime(线程活动保持时间):线程池的工作线程空闲后,保持存活的时间。

4)TimeUnit(线程活动保持时间的单位)。

5)runnableTaskQueue(任务队列)。
① ArrayBlockingQueue
② LinkedBlockingQueue
③ SynchronousQueue
④ PriorityBlockingQueue

6)ThreadFactory(线程工厂。用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程设置更有意义的名字)。

7)RejectedExecutionHandler(四种饱和策略):当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。
①AbortPolicy:直接抛出异常(默认)。
②CallerRunsPolicy:只用调用者所在线程来运行任务。
③DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
④DiscardPolicy:不处理,丢弃掉。

3、向线程池提交任务

execute()

execute 方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功。

submit()

submit 方法用于提交需要返回值的任务。线程池会返回一个future类型的对象,通过这个对象可以判断任务是否执行成功,并且可以通过future的get 方法来获取返回值。

4、线程池的生命周期

在这里插入图片描述
可以通过调用线程池的 shutdownshutdownNow 方法来关闭线程池。

1、线程池的五种状态,只能由小到大迁移,即-1 > 0 > 1 > 2 > 3 。

2、shutdown()不清空任务队列、会等它们完成, shutdownNow()会清空任务队列、不等它们完成。shutdown()只中断空闲的线程, shutdownNow()会中断所有的线程。

3、TIDYING 和 TREMINATED二者之间执行了一个钩子函数 terminated(),目前这是一个空的实现(自己可以去实现自己的业务需求)。

三:理解同步异步与阻塞非阻塞

同步:执行一个操作之后,等待结果,然后才继续执行后续的操作。

异步:执行一个操作后,可以去执行其他的操作,然后等待通知再回来执行刚才没执行完的操作。

阻塞:进程给CPU传达一个任务之后,一直等待CPU处理完成,然后才执行后面的操作。

非阻塞:进程给CPU传达任务后,继续处理后续的操作,隔断时间再来询问之前的操作是否完成。这样的过程其实也叫轮询。

下面举个段子说明一下

同步异步与阻塞非阻塞(段子)

老张爱喝茶,废话不说,煮开水。

出场人物:老张,水壶两把(普通水壶,简称水壶;会响的水壶,简称响水壶)。

1、老张把水壶放到火上,立等水开。(同步阻塞)
老张觉得自己有点傻

2、老张把水壶放到火上,去客厅看电视,时不时去厨房看看水开没有。(同步非阻塞)
老张还是觉得自己有点傻,于是变高端了,买了把会响笛的那种水壶。水开之后,能大声发出嘀~~~~的噪音。

3、老张把响水壶放到火上,立等水开。(异步阻塞)
老张觉得这样傻等意义不大

4、老张把响水壶放到火上,去客厅看电视,水壶响之前不再去看它了,响了再去拿壶。(异步非阻塞)老张觉得自己聪明了。

所谓同步异步,只是对于水壶而言。普通水壶,同步;响水壶,异步。
虽然都能干活,但响水壶可以在自己完工之后,提示老张水开了。这是普通水壶所不能及的。同步只能让调用者去轮询自己(情况2中),造成老张效率的低下。

所谓阻塞非阻塞,仅仅对于老张而言
立等的老张,阻塞;看电视的老张,非阻塞。

四:阻塞队列

详情介绍在下面的两篇文章:

生产者消费者模型:https://blog.csdn.net/A12115419/article/details/120152178

阻塞队列:
https://blog.csdn.net/A12115419/article/details/119760828

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小小本科生debug

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值