前言
实战总结
命名
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i++) {
final int finalI = i;
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ":" + finalI);
}
});
}
}复制代码
pool-1-thread-3:2
pool-1-thread-2:1
pool-1-thread-3:4
pool-1-thread-1:3
pool-1-thread-3:6
pool-1-thread-2:5
pool-1-thread-3:8
pool-1-thread-1:7
pool-1-thread-2:9
复制代码
static class DefaultThreadFactory implements ThreadFactory {
private static final AtomicInteger poolNumber = new AtomicInteger(1);
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
DefaultThreadFactory() {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
namePrefix = "pool-" +
poolNumber.getAndIncrement() +
"-thread-";
}
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r,
namePrefix + threadNumber.getAndIncrement(),
0);
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}复制代码
public class NamedThreadFactory implements ThreadFactory {
private final AtomicInteger threadNumber;
private final String name;
private final boolean isDaemon;
public NamedThreadFactory(String name) {
this(name, false);
}
public NamedThreadFactory(String name, boolean daemon) {
this.threadNumber = new AtomicInteger(1);
this.isDaemon = daemon;
this.name = name + "-thread-pool-";
}
public Thread newThread(Runnable r) {
Thread t = new Thread(r, this.name + this.threadNumber.getAndIncrement());
t.setDaemon(this.isDaemon);
if (t.getPriority() != Thread.NORM_PRIORITY){
t.setPriority(Thread.NORM_PRIORITY);
}
return t;
}
}复制代码
有名字的线程池-thread-pool-1:0
有名字的线程池-thread-pool-3:2
有名字的线程池-thread-pool-1:3
有名字的线程池-thread-pool-2:1
有名字的线程池-thread-pool-1:5
有名字的线程池-thread-pool-1:7
有名字的线程池-thread-pool-1:8
有名字的线程池-thread-pool-3:4
有名字的线程池-thread-pool-1:9
有名字的线程池-thread-pool-2:6复制代码
线程池的关闭
这个其实和jvm的关闭有关系,我们看下jvm的退出的几种情况:
我们自定义的线程池如果都是非守护线程在运行,那只要线程池没有结束,jvm是无法退出的,跟我们刚才的这个例子是一样的,非守护线程没有结束,jvm无法退出。
public class MyThreadBean implements DisposableBean{
ExecutorService executorService = Executors.newFixedThreadPool(3, new NamedThreadFactory("有名字的线程池"));
@Override
public void destroy() throws Exception {
executorService.shutdown();
}
}复制代码
/**
* Calls <code>shutdown</code> when the BeanFactory destroys
* the task executor instance.
* @see #shutdown()
*/
public void destroy() {
shutdown();
}
/**
* Perform a shutdown on the ThreadPoolExecutor.
* @see java.util.concurrent.ExecutorService#shutdown()
*/
public void shutdown() {
if (logger.isInfoEnabled()) {
logger.info("Shutting down ExecutorService" + (this.beanName != null ? " '" + this.beanName + "'" : ""));
}
if (this.waitForTasksToCompleteOnShutdown) {
this.executor.shutdown();
}
else {
this.executor.shutdownNow();
}
}复制代码
异常处理
子线程吞并异常
public class MyThreadBean{
private final static Logger logger = LoggerFactory.getLogger(MyThreadBean.class);
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2,3,45,
TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(3),new NamedThreadFactory("有名字的线程池"));
public void execute1(){
for (int i = 0; i < 10; i++) {
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
logger.error(e.getMessage(), e);
}
logger.info(Thread.currentThread().getName() + "执行结束");
}
});
}
}
public void execute2(){
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
logger.error(e.getMessage(), e);
}
logger.info(Thread.currentThread().getName() + "执行结束");
}
});
}
}
}).start();
}
}复制代码
看下分别执行execute1()和execute2()这两个方法,会出现什么样的结果
execute1()的输出结果:
execute2()的输出结果:
java线程的异常处理
Runnable的run方法的定义也是要求不抛出checked异常
public interface Runnable {
public abstract void run();
}复制代码
那如果run方法中抛出了RuntimeException要如何去处理?
在Thread中通常设置了一个默认的处理方式
public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(
new RuntimePermission("setDefaultUncaughtExceptionHandler")
);
}
defaultUncaughtExceptionHandler = eh;
}复制代码
那我们可以覆盖次方法并实现UncaughtExceptionHandler来定义我们自己的处理异常的逻辑:
public interface UncaughtExceptionHandler {
void uncaughtException(Thread t, Throwable e);
}复制代码
public void uncaughtException(Thread t, Throwable e) {
if (parent != null) {
parent.uncaughtException(t, e);
} else {
Thread.UncaughtExceptionHandler ueh =
Thread.getDefaultUncaughtExceptionHandler();
if (ueh != null) {
ueh.uncaughtException(t, e);
} else if (!(e instanceof ThreadDeath)) {
//我们看到这里会是我们经常在main方法写出的异常信息打印的内容,有点熟悉
System.err.print("Exception in thread \""
+ t.getName() + "\" ");
e.printStackTrace(System.err);
}
}
}复制代码
public static void main(String[] args) {
Thread.currentThread().setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("异常就在这里了");
}
});
int a = 1/0;
System.out.println("haha");
}复制代码
异常就在这里了
Process finished with exit code 1复制代码
public void execute2(){
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
logger.error(e.getMessage(), e);
}
logger.info(Thread.currentThread().getName() + "执行结束");
}
});
}catch (Exception e){
logger.info("提交任务出现异常", e);
}
}
}
}).start();
}
复制代码
execute方法执行的过程中只会抛RejectExecuteException,所以我们如果不想抛异常的话,也可以复写RejectedExecutionHandler来处理,比如这样:
public class SyncInfoRejectHandler implements RejectedExecutionHandler {
final Logger logger = LoggerFactory.getLogger(getClass());
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
BlockingQueue<Runnable> queue = executor.getQueue();
try {
//把任务重新放到队列中,不过这个更多的是定时任务处理数据,保证任务不被拒绝可以这么去处理
queue.put(r);
logger.info("SyncInfoRejectHandler.put a runnable to executor success");
} catch (InterruptedException e) {
logger.error("SyncInfoRejectHandler.rejectedExecution interrupt", e);
}
}
}复制代码
submit异常吞并
我们平时都是通过submit来提交一个Callable,那如果提交的是Runnable呢,为方便起见我们核心的代码都放在一起了
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
return new FutureTask<T>(runnable, value);
}
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
public static <T> Callable<T> callable(Runnable task, T result) {
if (task == null)
throw new NullPointerException();
return new RunnableAdapter<T>(task, result);
}
//最终FutureTask中的callable指向的是一个RunnableAdapter,而RunnableAdapter的call方法也是调用了我们传进来的task的run方法,返回的是null
static final class RunnableAdapter<T> implements Callable<T> {
final Runnable task;
final T result;
RunnableAdapter(Runnable task, T result) {
this.task = task;
this.result = result;
}
public T call() {
task.run();
return result;
}
}
复制代码
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
//执行的Callable,对应的t一定是Null
if (t == null && r instanceof Future) {
try {
Future future = (Future) r;
if (future.isDone()){
// 判断任务是否执行完成
future.get();
}
} catch (CancellationException ce) {
t = ce;
} catch (ExecutionException ee) {
t = ee.getCause();
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
}
}复制代码
队列的选择
这里我们只说下ArrayBlockingQueue和LinkedBlockingQueue,后续我们会对这两个队列详细的讲。
ArrayBlockingQueue
/** Main lock guarding all access */
final ReentrantLock lock;
/** Condition for waiting takes */
private final Condition notEmpty;
/** Condition for waiting puts */
private final Condition notFull;复制代码
/** Lock held by take, poll, etc */
private final ReentrantLock takeLock = new ReentrantLock();
/** Wait queue for waiting takes */
private final Condition notEmpty = takeLock.newCondition();
/** Lock held by put, offer, etc */
private final ReentrantLock putLock = new ReentrantLock();
/** Wait queue for waiting puts */
private final Condition notFull = putLock.newCondition();复制代码
线程数设置
这里网上有大量的篇幅在提到,可以推荐看下这篇文章:线程数究竟设置多少合理 这里我简单说下个人经验吧
- 结合当前任务的类型属于cpu密集型还是io密集型,具体哪种类型设置多少,这篇文章也都有计算的公式。不过这里提下,如果是cpu密集型的也可以考虑fork/join.
- 一定需要感知每个任务处理花费的时间,并且结合当前任务可以承受的qps是多少,通过时间以及qps来看队列应该设置多大,一定得保证在队列满的时候,最后一个任务的处理在超时可允许的时间范围内,不然如果队列里的其他任务处理没问题,等到处理最后一些任务完了,客户端都超时了,那说明队列设置太大了。
CountDownLatch 丢失事件
我们在处理一批任务的时候,往往会把任务进行partition,然后再交给每个线程去处理,那主线程需要等待所有的线程处理完,来统计本次处理的时间,以及其他统计的数据,差不多和下面这段代码类似:
public void execute3(){
List<Integer> data = new ArrayList<Integer>(100);
for (int i = 0; i < 100; i++) {
data.add(i + 10);
}
List<List<Integer>> partition = Lists.partition(data, 20);
final CountDownLatch countDownLatch = new CountDownLatch(partition.size());
for (final List<Integer> dataToHandle : partition) {
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
try{
for (Integer i : dataToHandle) {
doSomeThing(i);
}
}catch (Exception e){
logger.error(e.getMessage(), e);
}finally {
countDownLatch.countDown();
}
}
});
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
logger.error(e.getMessage(), e);
}
logger.info("任务执行结束...");
}复制代码
之前这么写代码没有出现过问题,直到最近出现问题才发现这么写会导致主线程无法结束的问题。我们看下,虽然在每个任务的finally中进行处理
countDownLatch.countDown();复制代码
public void execute3(){
List<Integer> data = new ArrayList<Integer>(100);
for (int i = 0; i < 100; i++) {
data.add(i + 10);
}
final List<List<Integer>> partition = Lists.partition(data, 20);
final CountDownLatch countDownLatch = new CountDownLatch(partition.size());
new Thread(new Runnable() {
@Override
public void run() {
for (final List<Integer> dataToHandle : partition) {
try {
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
try{
for (Integer i : dataToHandle) {
doSomeThing(i);
}
}catch (Exception e){
logger.error(e.getMessage(), e);
}finally {
countDownLatch.countDown();
}
}
});
} catch (RejectedExecutionException e) {
logger.error(e.getMessage(), e);
//处理完异常之后需要补充countDownLatch事件
countDownLatch.countDown();
}
}
}
}).start();
try {
countDownLatch.await();
} catch (InterruptedException e) {
logger.error(e.getMessage(), e);
}
logger.info("任务执行结束...");
}复制代码
自此,我们线程池这一块就讲完了,我们在分析线程池的源码的时候看到很多在修改线程池的状态或者workerCount的时候都是用的UnSafe来操作的,这个就引出了CAS,我们下一篇来讲下并发中的CAS。