作者:Mars酱
声明:本文章由Mars酱编写,部分内容来源于网络,如有疑问请联系本人。
转载:欢迎转载,转载前先请联系我!
前言
前两篇介绍了理论,这篇讲具体的实现方式。前言都是废话,直接上车~
简单粗暴
创建一个对象,继承Thread类,实现run函数,这个线程异步就做完了:
/**
* @author mars酱
*/
public class OrderThread extends Thread {
@Override
public void run() {
System.out.println("----------------------------");
System.out.println("老板: 那小子已经下单了,开工做面!");
System.out.println("----------------------------");
}
}
主函数代码:
/**
* @author mars酱
*/
public class NoodlesRestaurant {
public static void main(String[] args) {
System.out.println("你:老板,两碗面条。(潇洒扫一扫,6元巨款两碗面)");
OrderThread order = new OrderThread();
order.start();
System.out.println("你:我俩等下吃碗面,就去看个电影,然后再那个###酒店吧");
System.out.println("女友:你好坏坏");
}
}
运行一下:
线程异步就是这么简单粗暴,干净又卫生~
开门接客
如果来一对小情侣就创建一个对象,事情处理完还要销毁对象,这样反复创建、销毁还是很费体力的,我们遇到这种情况可以使用线程池处理,JDK里面的线程池是java.util.concurrent.ThreadPoolExecutor
对象。
我们平常工作中呢一般这样写:
ExecutorService executor = Executors.newFixedThreadPool(3);
ExecutorService executor = Executors.newCachedThreadPool();
ExecutorService executor = Executors.newSingleThreadExecutor();
但是实际上呢,这三种方式最终都是通过java.util.concurrent.ThreadPoolExcutor
来完成的,所以我一步到位了。
Executors 中创建三种线程:
newFixedThreadPool: 固定大小的线程池;
newCachedThreadPool: 线程数根据任务动态调整的线程池;
newSingleThreadExecutor: 单线程用的线程池;
看下java.util.concurrent.ThreadPoolExcutor
的构造函数吧:
构造函数的参数我给大家翻译翻译,体现一下我英语bia级水平:
corePoolSize
: 池中存活的线程数,范围在0 ~ Integer.MAX_VALUE之间,假设现在设置为10,那么新任务来不足10个线程就会创建,否则超过10个线程就会根据情况销毁到只剩10为止;
maximumPoolSize
: 最大允许的线程数,
keepAliveTime
: 当线程数大于核心数时,keepAliveTime是多余的空闲线程在终止之前等待新任务的最长时间;
unit
: 是修饰keepAliveTime的单位,时分秒那种修饰,具体可以看看TimeUnit里面的内容;
workQueue
: 队列对象。newSingleThreadExecutor() 和 newFixedThreadPool() 使用的是LinkedBlockingQueue对象队列,newCachedThreadPool()使用的是SynchronousQueue队列,因为newCachedThreadPool()的需求是可以动态调整队列大小的,而SynchronousQueue队列它没有容量,放入一个元素之后只有等它被取出来才能再放入一个;
threadFactory
: ThreadFactory对象是线程工厂,工厂就是来创建线程的;
handler
: 拒绝策略。当到达线程边界和队列容量而被阻止执行时要使用的处理策略,这里可用的四种策略:
四种拒绝策略:
- CallerRunsPolicy:让提交任务的线程自己去执行这个任务,也就意味着这个线程会因此而阻塞;
- AbortPolicy:拒绝执行这个任务,并且在提交任务的线程中抛出
java.util.current.RejectedExecutionException
异常; - DiscardPolicy:同样拒绝执行这个任务,但是不抛出异常;
- DiscardOldestPolicy:从任务队列中把最早加入的任务丢弃,然后把当前任务加进任务队列;
介绍完了,写代码玩玩:
/**
* @author mars酱
*/
public class NoodlesRestaurant {
public static void main(String[] args) {
// 1. 创建一个线程池,活跃线程5, 最大线程10,等待时长5秒,使用LinkedBlockingQueue队列,拒绝策略使用CallerRunsPolicy
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5,
10,
5,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(),
new ThreadPoolExecutor.CallerRunsPolicy());
// 2. 接待一个用户的下单
threadPoolExecutor.execute(new OrderThread());
}
}
执行后的结果:
很好,接1个客没问题,那么我们开始大量接客:
改造下单代码,增加桌号,以方便知道拿桌下单了:
public class OrderThread extends Thread {
Integer deskNum;
public OrderThread(Integer deskNum) {
this.deskNum = deskNum;
}
@Override
public void run() {
System.out.println(">> 老板: " + deskNum + "号桌已下单,开工做面!");
}
}
餐厅代码也改造一下:
/**
* @author mars酱
*/
public class NoodlesRestaurant {
public static void main(String[] args) {
// System.out.println("你:老板,两碗面条。(潇洒扫一扫,6元巨款两碗面)");
// OrderThread order = new OrderThread();
// order.start();
//
// System.out.println("你:我俩等下吃碗面,就去看个电影,然后再那个###酒店吧");
// System.out.println("女友:你好坏坏");
// 1. 创建一个线程池,活跃线程5, 最大线程10,等待时长5秒,使用LinkedBlockingQueue队列,拒绝策略使用CallerRunsPolicy
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5,
10,
5,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(),
new ThreadPoolExecutor.CallerRunsPolicy());
// 2. 模拟10桌客人轮流下单点餐
for (int i = 0; i < 10; i++) {
System.out.println(i + "桌 | 潇洒扫一扫~");
threadPoolExecutor.execute(new OrderThread(i));
System.out.println(i + "桌 | 点餐完成!");
}
}
}
运行结果:
最后
废话很少,让你拥有充足的一分钟,连代码中互撩的环节也省去了。实际工作中,当应用结束的时候,有两个问题要考虑:
- 线程池队列当中未被处理的任务要如何处理?
- 正在执行的那些任务要如何处理?
我认为在设计上应该保证,队列中未被处理的任务是可以随时丢弃的。下次启动线程池后,这些未处理的任务可以重新再提交给线程池,业务上不受影响。另外,对于正在执行的任务,要保证在执行任务过程中,一旦进程被结束,那么要有应对的处理策略:
- 所有已经作出的状态变更都会回滚,下次执行这个任务又可以再开始。例如:使用数据库事务
- 这个任务可以从中间状态继续执行,比如:下载文件中的断点续传。
但不管怎么样,当进程结束时,还是希望应该尽可能等待正在处理的任务执行完成,以减少出错的可能性。ThreadPoolExecutor 提供这样一种方式:
// 告诉线程池不再接受新的任务,也不再处理队列中的任务
executor.shutdownNow();
// 等待线程池中正在执行的任务都处理完毕,最多1小时
executor.awaitTermination(1, TimeUnit.HOURS);
再最后
多线程异步虽然有缺陷,但是如果你不关心返回结果,而且任务之间相互没有依赖关系,那么多线程异步是个简单粗暴的可靠选择。