【摘要】 Callable接口我们知道,java中实现多线程的方式有好几种,下面我们就来看看Callable接口代码很简单:// 实现Callable接口class Demo implements Callable<Integer> { @Override public Integer call() { System.out.println(“come in Callable…
Callable接口
我们知道,java中实现多线程的方式有好几种,下面我们就来看看Callable接口
代码很简单:
// 实现Callable接口
class Demo implements Callable<Integer> {
@Override
public Integer call() {
System.out.println(“come in Callable”);
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
return 1024;
}
}
用法也很简单
// 声明一个FutureTask接口才可以执行实现Callable接口的类
// FutureTask可以复用,如果有多个线程调用了futureTask,只会执行一次,
// 如果要执行多次,那么就需要声明多个FutureTask
FutureTask<Integer> futureTask = new FutureTask<>(new Demo());
Thread thread = new Thread(futureTask, “a”);
thread.start();
我们要强调的重点是它和Runnable接口的区别
// 如果a线程没有运行完,那么就等着
while (!futureTask.isDone()) {
// Callable可以获取到返回值,但是Runnable没有办法获取到返回值
// 两个线程:一个main线程,一个a线程,
try {
int result01 = 123;
// 如无必要,建议放到最后,要求获得a线程的运行结果,
// 如果没有获取到,就会阻塞线程,直到获取到结果,所以放到最后,可以防止线程阻塞
System.out.println(futureTask.get() + result01);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
结果:
首先,我们要知道,我们的电脑是一个cpu对应多个核心数,具体怎么理解呢?可以参考下面的说明
核,你可以想象成人脑子,一个核就是一个脑子,四核就说明CPU有四个脑子,脑子越多解决问题的速度越快,Intel的核技术,可以把一个核模拟分成两个线程来用,充分的发挥了cpu的性能,线程8就代表核心数是4核的处理器可以模拟出8个线程来使用。线程我们可以理解每个大脑同时能处理多少件事。
// 查看当前电脑是几核的
System.out.println(Runtime.getRuntime().availableProcessors());
而我们日常生产环境中,基本不会单一的创建单个线程,而是通过线程池的方式帮助我们去创建和管理线程,那么线程池的作用和好处有哪些呢?
线程池的作用:控制运行的线程数量
线程池的特点:线程复用,控制最大并发数量,管理线程
线程池的处理过程:先将任务放入队列,在线程创建后启动这些任务,如果线程数量超过了最大数量,那么超出的数量线程排队等候,
等待其他线程执行完成后,再从队列中取出任务来执行
线程池的好处:
1、降低资源消耗。通过重复利用已经创建的线程降低线程创建和消耗造成的消耗
2、提高响应速度。当任务到达时,不需要等待线程创建就能立即执行
3、提高线程的可管理性,线程是稀缺资源,如果无限的创建,不仅会消耗系统资源,且还会降低系统的稳定性,使用线程池可以进行同一的分配,调优和监控
4、底层都是ThreadPoolExecutor
常见的线程池的种类:
// 线程池固定线程数量
ExecutorService service = Executors.newFixedThreadPool(3);
// 线程池内只有1个线程
ExecutorService service2 = Executors.newSingleThreadExecutor();
// 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
ExecutorService service3 = Executors.newCachedThreadPool();
try {
// execute和submit都属于线程池的方法,execute只能提交Runnable类型的任务,而submit既能提交Runnable类型任务也能提交Callable类型任务。
// execute会直接抛出任务执行时的异常,submit会吃掉异常,可通过Future的get方法将任务执行时的异常重新抛出。
// execute所属顶层接口是Executor,submit所属顶层接口是ExecutorService
// 实现类ThreadPoolExecutor重写了execute方法,抽象类AbstractExecutorService重写了submit方法。
for (int i = 0; i < 6; i++) {
service.execute(() -> System.out.println(Thread.currentThread().getName()));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
service.shutdown();
}
提交任务后,线程池会做如下判断:
1、如果正在运行的线程数量小于coreSize,那么会马上创建线程运行这个任务
2、如果正在运行的线程数量大于或等于coreSize,那么会将这个任务放入阻塞队列中
3、如果这个时候队列满了,且在运行的线程数量还小于maximumPoolSize,那么会创建非核心线程来立刻执行这个任务
4、如果队列满了,且在运行的线程数量大于等于maximumPoolSize,那么线程池会启动饱和拒绝策略来执行
jdk内置的拒绝策略(4种):
第一种:AbortPolicy(默认),直接抛异常
第二种:CallerRunsPolicy,不抛弃任务,不抛出异常,而是将某些任务回退到调用方
第三种:DiscardOldestPolicy,抛弃队列中等待最久的任务,然后把当前任务加入到队列中再次尝试提交当前任务
第四种:DiscardPolicy,直接丢弃任务,不处理也不抛异常。如果允许任务丢失,那么这是一种最好的方案
int corePoolSize, 常驻核心数
int maximumPoolSize, 最大核心数
long keepAliveTime, 多余的空闲线程的存活时间,当线程中的线程数量超过corePoolSize时,当空闲时间到达keepAliveTime时,多余
空闲线程会被销毁直到只剩corePoolSize个线程为止
TimeUnit unit, keepAliveTime的单位
BlockingQueue workQueue, 阻塞队列,用于存储多余的任务
ThreadFactory threadFactory, 创建线程的线程工厂,一般默认就行
RejectedExecutionHandler handler, 拒绝策略