场景
在日常的系统中,使用前的第一步往往是需要注册,只有注册后获得登录账号和密码才能正常使用系统,有些系统在注册成功后,还会往用户的手机发送一条注册成功的短信通知,这个并不属于主线流程,也就是说发不发送该短信通知,用户都已经能登录系统了,那么如果是单线程的情况下(如图1),如果在发送短信这一步服务出现问题了(调用第三方的短信服务,异常往往是不可控的,可能是欠费了,可能是对方服务挂了),那么整个流程就挂了,那应该如何解决呢?
解决方案
大致的解决思路是将这个短信服务从注册主流程中抽离出来,单独执行,当注册后立刻给客户端返回注册成功与否的信息(如图二),解决方案有很多种,如定时器+数据表轮询发送、多线程方式、工作流方式等等,这里先说下多线程方式的简单实现思路。
直接调用线程的方式
package com.alex.examples.exceptions;
public class TestThree {
public static void main(String[] args) {
String msg = test();
System.out.println(msg);
}
public static String test() {
TestThree testThree = new TestThree();
testThree.thread();
return "注册成功";
}
/**
* 直接调用线程
*/
public void thread() {
new Thread(() -> {
try {
System.out.println("执行线程,发短信中。。。。。");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
自定义线程池的方式
package com.alex.examples.exceptions;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* 自定义线程池
*/
public class CommonThreadPool {
/**
* 线程池对象
*/
public static ExecutorService pool = null;
/**
* 线程池核心池的大小
*/
private static final int CORE_POOL_SIZE = 5;
/**
* 获取当前系统的CPU 数目
*/
private static int cpuNums = Runtime.getRuntime().availableProcessors();
/**
* 线程池的最大线程数
*/
private static final int MAX_POOL_SIZE = (cpuNums * 2) > CORE_POOL_SIZE ? (cpuNums * 2) : CORE_POOL_SIZE;
static {
pool = new ThreadPoolExecutor(
CORE_POOL_SIZE, // 核心线程数
MAX_POOL_SIZE, // 最大线程数 通常核心线程数=最大线程数 当MAX_POOL_SIZE > CORE_POOL_SIZE 时,并不是当核心线程全部被占用后立马开始创建新的线程(只有在队列也满了之后才会开始新建线程)
0L, // 存活时间 >=0 0 永不回收【非核心线程除外】
TimeUnit.MILLISECONDS, // 单位
new ArrayBlockingQueue<Runnable>(100), // 队列 存放待执行任务
new ThreadFactoryBuilder().setNameFormat("CommonThread-%d").build(), // 创建工厂
new ThreadPoolExecutor.AbortPolicy()); // 拒绝策略 默认拒绝 丢弃 丢弃最老的 主线程执行
}
}
package com.alex.examples.exceptions;
public class TestThree {
public static void main(String[] args) {
String msg = test();
System.out.println(msg);
}
public static String test() {
TestThree testThree = new TestThree();
testThree.customThreadPool();
return "注册成功";
}
/**
* 调用自定义线程池,此处采用的是lambda写法
*/
public void customThreadPool() {
/**
* execute和submit的区别
* 1. execute只能提交Runnable类型的任务,没有返回值,而submit既能提交Runnable类型任务也能提交Callable类型任务,返回Future类型。
* 2. execute方法提交的任务异常是直接抛出的,而submit方法是是捕获了异常的,当调用FutureTask的get方法时,才会抛出异常。
*/
CommonThreadPool.pool.execute(() -> {
try {
System.out.println("执行线程,发短信中。。。。。");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
CommonThreadPool.pool.submit(() -> {
try {
System.out.println("执行线程,发短信中。。。。。");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}
结果如下
注意
1.线程用完理论上是要关闭的,但关不关闭视具体项目情况而定;
1、interrupt()
最正确的停止线程的方式是使用 interrupt。
但 interrupt仅仅起到通知被停止线程的作用。
而对于被停止的线程而言,它拥有完全的自主权,它既可以选择立即停止,也可以选择一段时间后停止,也可以选择不停止。2、shutdown()
调用 shutdown() 方法之后线程池不会立刻就被关闭,因为这时线程池中可能还有很多任务正在被执行,或是任务队列中有大量正在等待被执行的任务,调用 shutdown() 方法后线程池会在执行完正在执行的任务和队列中等待的任务后才彻底关闭。3、shutdownNow()
如果想要马上关闭线程池,不管正在执行的任务和排队等待的任务,那么shutdownNow则是你最好的选择,强就强在NOW。