为什么要使用多线程呢,简而言之就是把多个任务并行执行,比如蒸馒头的同时炒菜,这样就提高了效率,能早点吃上饭hh。不用多线程的话只能一个接一个的执行任务。

创建一个线程
// Runneable:线程要执行的任务
Runneable r = new Runnable() {
@Override
public void run() {
System.out.println("蒸馒头。。。");
}
};
// 创建线程:放入执行的任务
Thread t = new Thread(r);
Runnable是线程要执行的任务,当线程t启动后,将会在t线程内执行Runnable接口中的run方法。
如果你的jdk版本是8以上的话,使用Lambda表达式,上述代码可以简化如下:
Thread t = new Thread(() -> {
System.out.println("蒸馒头。。。");
});
启动线程
调用线程的start()方法即可:
// 启动线程
t.start();
线程启动后,会自动执行Runnable中的run()方法。
FutureTask
Runnable中的run()方法是没有返回结果的,也无法抛出异常,因此我们如果想获取线程的执行结果,需要用到FutureTask类:

可以看到FutureTask实现了Runnable接口(实线表示继承,虚线表示实现),这样就可以放入Thread中执行啦。
FutureTask的构造函数需要接收一个Runnable类型或者Callable类型的对象为参数:

Callable接口源码如下:
@FunctionalInterface
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
可以看到Callable中的call()方法有返回值,还可以抛出异常。
Callable也是线程要执行的任务,线程启动后调用FutureTask的run()方法,而run方法内部又调用了Callable中的call()方法。
FutureTask和Callable的用法如下:
// Callable:线程要执行的任务
Callable<String> c = new Callable<String>() {
@Override
public String call() throws Exception {
// 做些什么事
return "时光那个匆匆";
}
};
// 定义及实例化一个FutureTask
FutureTask<String> f = new FutureTask<>(c);
// 启动线程
Thread t = new Thread(f);
t.start();
try {
// 获取执行结果
String result = f.get();
} catch (Exception e) {
// 异常情况:
// 当前线程阻塞时被中断,报InterruptedException异常;
// call()执行过程中抛异常:报ExecutionException异常;
// get()前任务已被取消,报CancellationException异常;
e.printStackTrace();
}
get()方法:获取Callable中call()方法的返回结果,如果还没执行完,调用者线程将处于WAITING状态。
线程池
以上是单独的创建一个线程,通常情况下,面对大量的并发请求,如果创建过多线程,不仅会占用很多内存,而且操作系统将会花费大量时间用在创建线程、销毁线程、切换线程上下文上,影响性能,正确的办法是使用线程池管理线程。
Executor是Java线程池相关API中最顶层的接口,ThreadPoolExecutor是它的最基础、标准的实现类,我们来看下这个类的用法:
首先创建一个线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
5, //核心线程数
10, //最大线程数
5, //keepAliveTime,超过核心线程数的线程,超过 keepAliveTime,就会被销毁
TimeUnit.SECONDS, //keepAliveTime的单位
new LinkedBlockingQueue<>(10) //工作队列,线程池执行的任务将会放到这个队列中
);
放入一个任务并执行
// 创建一个要执行的任务,这里使用了Lambda表达式(为了简化代码)
Callable<String> c = () -> {
// 做些什么事
return "执行结果";
};
// 执行任务
Future<String> f = threadPoolExecutor.submit(c);
//获取执行结果
String r = f.get();
可以看到submit()方法返回了一个Future对象(在方法内部创建了一个FutureTask对象并放入线程执行,然后返回了这个对象,FutureTask实现了Future接口),然后就可以使用对象中的get()方法来获取执行结果了。
也可以放入一个任务集合,同样的,也会返回一个Future的集合:
// 任务集合
List<Callable<String>> taskList = new ArrayList<>();
// 往集合中放入任务
taskList.add(c);
// 执行任务
try {
List<Future<String>> futureList = threadPoolExecutor.invokeAll(taskList);
} catch (InterruptedException e) {
e.printStackTrace();
}
一个例子
有这样一个需求:后端需要开发一个接口,返回用户的所有相关信息,如下:

后台处理逻辑:
1、收到请求;
2、调用用户服务获取用户基本信息;
3、调用资产服务获取余额信息;
4、调用活动服务获取优惠券信息;
...
5、最后,汇总所有信息,一起返回到前台。
其中2、3、4步可以使用多线程并发执行,这样相比于单个线程依次执行,所需时间将会大大缩小。
开始敲代码!
public Map<String, Object> getInfo(){
// 创建线程
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
5, //核心线程数
10, //最大线程数
5, //keepAliveTime,超过核心线程数的线程,超过 keepAliveTime,就会被销毁
TimeUnit.SECONDS, //keepAliveTime的单位
new LinkedBlockingQueue<>(10) //工作队列,线程池执行的任务将会放到这个队列中
);
Callable<String> c1 = () -> {
// 调用用户服务获取基本信息
String r = getUserInfo();
return r;
};
// 执行任务
Future<String> f1 = threadPoolExecutor.submit(c1);
Callable<String> c2 = () -> {
// 调用资产服务获取余额
String r = getMoney();
return r;
};
// 执行任务
Future<String> f2 = threadPoolExecutor.submit(c2);
Callable<String> c3 = () -> {
// 调用活动服务获取优惠券
String r = getCoupon();
return r;
};
// 执行任务
Future<String> f3 = threadPoolExecutor.submit(c3);
//数据汇总,获取三个子线程的返回结果
Map<String, Object> map = new HashMap<>();
try {
String userInfo = f1.get();
String money = f2.get();
String coupon = f3.get();
map.put("userInfo", userInfo);
map.put("money", money);
map.put("coupon", coupon);
} catch (Exception e) {
e.printStackTrace();
}
return map;
}
线程池中的核心线程数和最大线程数如何设置呢,通常需要根据具体情况而定,考虑的因素有CPU的核数(4核?8核?)、任务类型(计算型任务?IO型任务?)、单个任务执行时间等等,必要的话还可以通过压力测试来寻找最佳的参数值。
网上找了两篇文章,感觉写的挺好,可以参考下^^:
线程池中各参数的意义:Java线程池的核心线程数和最大线程数总是容易混淆怎么办
如何确定线程数:线程池线程数目的确定
网友评论