本文将涵盖两个主题:

  • 通过实现Callable接口创建线程

  • 在Java中使用Executor框架

实现Callable接口

为了创建一段可以在线程中运行的代码,我们创建了一个类,然后实现了Callable接口。这段代码完成的任务需要放在call()函数中。在下面的代码中,你可以看到Callable task是一个实现Callable接口的类,在函数中完成了将0到4之间的数字相加的任务。

package com.mzc.common.thread;

import java.util.concurrent.Callable;

/**
* <p class="detail">
* 功能: 实现Callable接口
* </p>
*
* @author Moore
* @ClassName Callable task.
* @Version V1.0.
* @date 2019.12.23 10:06:23
*/
public class CallableTask implements Callable<Integer> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i < 5; i++) {
sum += i;
}
return sum;
}
}

在上面的代码中,你会注意到Callable的参数为Integer。这表明此Callable的返回类型将为Integer。还可以看到调用函数返回了Integer类型。

1、创建线程并运行

下面的代码显示了如何创建线程,然后运行它们。

package com.mzc.common.thread;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask; public class CallableInterfaceDemo {
public static void main(String[] args) {
FutureTask<Integer>[] futureList = new FutureTask[5];
for (int i = 0; i <= 4; i++) {
Callable<Integer> callable = new CallableTask();
futureList[i] = new FutureTask<Integer>(callable);
Thread t = new Thread(futureList[i]);
t.start();
}
for (int i = 0; i <= 4; i++) {
FutureTask<Integer> result = futureList[i];
try {
System.out.println("Future Task" + i + ":" + result.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
}

为了创建线程,首先我们需要创建一个CallableTask实例,该实例实现CallableI接口,如图所示:

Callable<Integer> callable = new CallableTask();

然后我们需要创建一个FutureTask类的实例,并将Callable Task的实例作为参数传递,如下所示:

futureList[i] = new FutureTask<Integer>(callable);

然后创建一个线程,我们创建一个Thread类的实例,并将FutureTask类的实例作为参数传递,如下所示:

Thread t = new Thread(futureList[i]);

最后,使用start()方法启动线程。

2、从线程获取结果

如果是Callables,线程实际上可以返回一个值。为了获得该值,我们可以在FutureTask实例上调用get()函数。在我们的代码中,线程的返回值是0到4之间的数字之和。如下面的代码片段所示:

FutureTask<Integer> result = futureList[i];
try {
System.out.println("Future Task" + i + ":" + result.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}

线程可能抛出异常,可以使用try catch块进行处理。

运行结果:


Future Task0:10
Future Task1:10
Future Task2:10
Future Task3:10
Future Task4:10

小课堂

上面示例中,我们使用了new Thread()方式创建一个线程,但在实际项目中我们很少这样用,因为每次new的时候都是新建了一个线程对象,并且不能复用,只能释放线程资源,这种方式性能较差,而且如果不加限制,创建太多的线程会消耗太多系统资源,同时线程太多,如果要定期执行、关闭线程等,都不方便统一管理。

所以我们就需要线程池来管理线程,使用线程池有以下优点:

  • 可以复用线程,减少对象创建,就减少系统资源消耗

  • 可以控制最大并发线程数,提高系统资源利用率

  • 使用线程池可以避免资源竞争,也就减少了阻塞情况

  • 创建线程池简单方便,同时便于统一管理

Executor框架

每次创建线程都是资源密集型的。一个很好的替代方法是提前设置好一些线程,也就是我上面说的线程池,然后将我们的任务分配给这些线程。这就是Executors类和ExecutorService非常有用的地方。

 
image.png

上图显示了具有4个线程的线程池。每当我们希望运行任何任务时,都可以将其分配给这些线程。任务完成后,线程将被释放以执行其他任务。

1、如何使用Executor框架

我们还是先复用上面实现了Callable接口的CallableTask实例,然后我们改造一下创建线程方式,不再使用new Thread() 创建一个线程,而是先创建一个ExecutorService,Executors类具有ExecutorService的多个实现,这儿我们使用Executors类创建大小为4的固定线程池(newFixedThreadPool)。

ExecutorService executors = Executors.newFixedThreadPool(4);

接下来,我们需要将我们的任务提交给ExecutorService,像这样:

Future<Integer> future = executors.submit(w);

提交任务后,我们将获得一个Future对象的实例。Future对象将存储Task的结果。完整代码:

package com.mzc.common.thread;

import java.util.concurrent.*;

/**
* <p class="detail">
* 功能: 使用Executor创建多线程
* </p>
*
* @author Moore
* @ClassName Executor demo.
* @Version V1.0.
* @date 2019.12.23 10:54:31
*/
public class ExecutorDemo { public static void main(String[] args) {
/**
* 创建线程池的6种方式
*/ // 1、创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
ExecutorService executors = Executors.newFixedThreadPool(4); // 2、创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
// ExecutorService executors = Executors.newCachedThreadPool(); // 3、创建一个定长线程池,支持定时及周期性任务执行。
// ExecutorService executors = Executors.newScheduledThreadPool(4); // 4、创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
// ExecutorService executors = Executors.newSingleThreadExecutor(); // 5、newWorkStealingPool工作窃取线程池,它是新的线程池类ForkJoinPool的扩展,
// 但是都是在统一的一个Executors类中实现,由于能够合理的使用CPU进行对任务操作(并行操作),
// 所以适合使用在很耗时的任务中
// ExecutorService executors = Executors.newWorkStealingPool(4); // 6、newSingleThreadScheduledExecutor创建线程池同时放入多个线程时,每个线程都会按照自己的调度来执行,
// 但是当其中一个线程被阻塞时,其它的线程都会受到影响被阻塞,
// 不过依然都会按照自身调度来执行,但是会存在阻塞延迟。
// ExecutorService executors = Executors.newSingleThreadScheduledExecutor();
Future<Integer>[] futures = new Future[5];
Callable<Integer> w = new CallableTask();
try {
for (int i = 0; i < 5; i++) {
Future<Integer> future = executors.submit(w);
futures[i] = future;
} for (int i = 0; i < futures.length; i++) {
try {
System.out.println("Result from Future " + i + ":" + futures[i].get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
} finally {
executors.shutdown();
}
}
}

2、获取结果

为了获得每个任务的结果,我们可以调用Future实例的get()方法,方法和上面的一样:

for (int i = 0; i < futures.length; i++) {
try {
System.out.println("Result from Future " + i + ":" + futures[i].get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}

运行结果

Result from Future 0:10
Result from Future 1:10
Result from Future 2:10
Result from Future 3:10
Result from Future 4:10

固定线程池(newFixedThreadPool)的运行情况说明:

  • 在上面的示例中,我们创建了一个大小为4的固定线程池。

  • 如果我们总共向ExecutorService提交3个任务,那么所有3个任务都将分配给线程池,并且它们将开始执行。

  • 如果我们向ExecutorService提交4个任务,那么所有这4个任务将再次分配给线程池,并且它们将开始执行。

  • 如果我们向该线程池提交5个任务,只有4个任务将分配给线程池。这是因为线程池的大小为4。仅当释放池中的线程之一时,才会分配第五个任务。

关闭ExecutorService

当我们不再需要线程时,需要关闭ExecutorService,这样做能确保JVM不会消耗其他资源。我们可以使用下面这个命令关闭ExecutorService:

executors.shutdown();

ExecutorService的关闭操作通常位于``finally''块中。这是为了确保即使在发生任何异常的情况下,关闭操作始终在代码的末尾被执行。如果关闭操作不正确,那么如果发生任何异常,则ExecutorService仍将运行并消耗其他JVM资源。

总结

线程池的一些常用方法

  • submit():提交任务,能够返回执行结果execute+Future

  • shutdown():关闭线程池,等待任务都执行完

  • getPoolSize():线程池当前线程数量

  • getActiveCount():当前线程池中正在执行任务的线程数量

  • shutdownNow():关闭线程池,不等待任务执行完

  • getTaskCount():线程池已执行和未执行的任务总数

  • getCompletedTaskCount():已完成的任务数量

创建线程池的6种方式

  1. newFixedThreadPool():创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

  2. EnewCachedThreadPool():创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

  3. newScheduledThreadPool():创建一个定长线程池,支持定时及周期性任务执行。

  4. newSingleThreadExecutor():创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO,* LIFO*,优先级)执行。

  5. newWorkStealingPool():工作窃取线程池,它是新的线程池类ForkJoinPool的扩展,但是都是在统一的一个Executors类中实现,由于能够合理的使用CPU进行对任务操作(并行操作),所以适合使用在很耗时的任务中。

  6. newSingleThreadScheduledExecutor():newSingleThreadScheduledExecutor创建线程池同时放入多个线程时,每个线程都会按照自己的调度来执行,但是当其中一个线程被阻塞时,其它的线程都会受到影响被阻塞,不过依然都会按照自身调度来执行,但是会存在阻塞延迟。

代码示例

// 1、创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
ExecutorService executors = Executors.newFixedThreadPool(4); // 2、创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
ExecutorService executors = Executors.newCachedThreadPool(); // 3、创建一个定长线程池,支持定时及周期性任务执行。
ExecutorService executors = Executors.newScheduledThreadPool(4); // 4、创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
ExecutorService executors = Executors.newSingleThreadExecutor(); // 5、newWorkStealingPool工作窃取线程池,它是新的线程池类ForkJoinPool的扩展,
// 但是都是在统一的一个Executors类中实现,由于能够合理的使用CPU进行对任务操作(并行操作),
// 所以适合使用在很耗时的任务中
ExecutorService executors = Executors.newWorkStealingPool(4); // 6、newSingleThreadScheduledExecutor创建线程池同时放入多个线程时,每个线程都会按照自己的调度来执行,
// 但是当其中一个线程被阻塞时,其它的线程都会受到影响被阻塞,
// 不过依然都会按照自身调度来执行,但是会存在阻塞延迟。
ExecutorService executors = Executors.newSingleThreadScheduledExecutor();

**

如果觉得本文觉得还行,可以关注我的个人公众号:码之初。给与我更多写下去的动力,谢谢!码之初将为您奉献:

  • Java基础到架构知识全方位涵盖

  • 海量面试资料,从文档到视频,从基础到底层,面试无忧

  • 程序猿生活记录,程序猿的生活也值得关注

  • 不定期的福利发放,惊喜就是在不经意间来临。

 

使用Java Executor框架实现多线程的更多相关文章

  1. Java Executor框架使用

    Java Executor框架是Jdk1.5之后推出的,是为了更加方便的开发多线程应用而封装的框架: 相比传统的Thread类,Java Executor使用方便,性能更好,更易于管理,而且支持线程池 ...

  2. Java Executor 框架

    Java Executor 框架 Executor框架是指java5中引入的一系列并发库中与executor相关的功能类,包括Executor.Executors. ExecutorService.C ...

  3. Java Executor 框架学习总结

    大多数并发都是通过任务执行的方式来实现的.一般有两种方式执行任务:串行和并行. class SingleThreadWebServer { public static void main(String ...

  4. Java Executor框架

    java.util.concurrent 包中包含灵活的线程池实现,但是更重要的是,它包含用于管理实现 Runnable 的任务的执行的整个框架,该框架称为 Executor 框架.该框架基于生产者- ...

  5. Java并发和多线程(二)Executor框架

    Executor框架 1.Task?Thread? 很多人在学习多线程这部分知识的时候,容易搞混两个概念:任务(task)和线程(thread). 并发编程可以使我们的程序可以划分为多个分离的.独立运 ...

  6. Java多线程学习(八)线程池与Executor 框架

    目录 历史优质文章推荐: 目录: 一 使用线程池的好处 二 Executor 框架 2.1 简介 2.2 Executor 框架结构(主要由三大部分组成) 2.3 Executor 框架的使用示意图 ...

  7. 【Java多线程】Executor框架的详解

    在Java中,使用线程来异步执行任务.Java线程的创建与销毁需要一定的开销,如果我们为每一个任务创建一个新线程来执行,这些线程的创建与销毁将消耗大量的计算资源.同时,为每一个任务创建一个新线程来执行 ...

  8. java并发编程(十七)Executor框架和线程池

    转载请注明出处:http://blog.csdn.net/ns_code/article/details/17465497   Executor框架简介 在Java 5之后,并发编程引入了一堆新的启动 ...

  9. 转:【Java并发编程】之十九:并发新特性—Executor框架与线程池(含代码)

      Executor框架简介 在Java5之后,并发编程引入了一堆新的启动.调度和管理线程的API.Executor框架便是Java 5中引入的,其内部使用了线程池机制,它在java.util.coc ...

随机推荐

  1. [webpack]解决报错 CleanWebpackPlugin is not a constructor

    错误写法 const CleanWebpackPlugin = require("clean-webpack-plugin"); 正确写法: let {CleanWebpackPl ...

  2. C#如何控制panel加载的字窗体中控件的位置随着显示器分辨率大小而改变

    System.Windows.Forms.Screen   screen=Screen.PrimaryScreen;     System.Drawing.Rectangle   rct=screen ...

  3. (五)AJAX技术

    一.定义 AJAX 是一种用于创建快速动态网页的技术. 通过在后台与服务器进行少量数据交换,AJAX 可以使网页实现异步更新.这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新. 传统的 ...

  4. es6 fetch方法请求接口

    fetch(url, { method: 'post', headers: { 'Content-type': 'application/x-www-form-urlencoded; charset= ...

  5. 【c# 学习笔记】构造函数

    构造函数 主要用于创建类的实例对象.当调用构造函数创建一个对象时,构造函数会为对象分配内存空间,并初始化类的成员.构造函数分为实例构造函数和静态构造函数两种. 1.实例构造函数 实例构造函数用于创建和 ...

  6. C#数字除法

    C#中计算double a=1/1000:应该结果是0.001,但为什么会变成0呢? C# 中 如果相除的两个数都是整数(int 型) 那么除的结果就是只取整数部分 所以你才会取到0 如果你要取精确的 ...

  7. 【长期更新】Python使用随想笔记

    Q1:python函数传参是传值还是传引用? A:Python参数传递采用的肯定是"传对象引用"的方式.这种方式相当于传值和传引用的一种综合.python不允许程序员选择采用传值还 ...

  8. csu 1756: Prime

    1756: Prime Submit Page   Summary   Time Limit: 3 Sec     Memory Limit: 128 Mb     Submitted: 281    ...

  9. fastadmin 后台view data-source关联报500错误问题

    data-source 关联模型通过id关联被关联表的name字段,没有name字段就报500

  10. 了解 DML、DDL、DCL

    一.DML DML(Data Manipulation Language)数据操纵语言: 最经常用到的 SELECT.UPDATE.INSERT.DELETE. 主要用来对数据库的数据进行一些操作 S ...