首先,线程池是啥,有啥好处这里就不提了.google一下马上知道. 嘻嘻嘻
首先第一步!我们先来认识下 在 java.util.concurrent
包中,提供了 ThreadPoolExecutor
的实现。
该类是核心,参数以及含义要多多理解并记下. 源代码如下:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
参数说明:
prestartAllCoreThreads 方法
下面我使用一个线程池实现累加的操作讲解线程池的使用,并且打印出来当前活跃线程数以及执行完成的任务数量来帮助大家理解线程池的使用
package allen.interview.thread.pool.sourceCodeLearn;
import java.text.MessageFormat;
import java.util.concurrent.*;
/**
* java.util.concurrent.Executors 包下的 newCachedThreadPool使用例子
* 该例子场景描述: 简单的使用到信号量 做1000次加一操作
*
*/
public class CachedThreadPoolExample {
/**
* 首先贴出使用ThreadPoolExecutor构造函数创建的四个参数
* return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
* 60L, TimeUnit.SECONDS,
* new SynchronousQueue());
*
* 核心线程数:0
* 最大线程数量:int最大值,也就是说基本不限制
* 线程最大空闲时间:60
* 空闲时间单位:seconds
* 阻塞队列: SychronousQueue 一个不保存Runnable的队列,只有当有另外一个线程做取操作的时候才可以入队里,并且默认阻塞的线程
* 是非公平形式的阻塞队列
*/
private static ExecutorService cachedThreadPool=Executors.newCachedThreadPool();
private static int count =0;
public static void main(String[] args) throws InterruptedException {
//信号量控制最大并发量200,把信号量理解成车库门卫就可以了 总共两百个车位可以使用
Semaphore semaphore = new Semaphore(200);
//倒数阀门,类似于3 ,2, 1 芝麻开门的功能, 只有这1000个线程每个都报数了(也就是执行countDown方法)
CountDownLatch countDownLatch = new CountDownLatch(1000);
for (int i = 0; i < 1000; i++) {
//!!!!重点来了,向我们的线程池提交一个Runnable的run方法,首先看线程池中有没有空闲的已创建的线程
//如果有使用现成的线程, 如果没有则创建新的线程来执行
cachedThreadPool.submit(() -> {
try {
semaphore.acquire();//获取到信号量才可以进行加1操作
Thread.sleep(1000);//模拟一个耗时操作,世界上最难的事是装作自己很忙的样子
add();
semaphore.release();//执行完释放信号量
countDownLatch.countDown();//不要忘了报数
} catch (InterruptedException e) {
e.printStackTrace();
}
});
int threadCount = ((ThreadPoolExecutor)cachedThreadPool).getActiveCount();
long taskCount = ((ThreadPoolExecutor)cachedThreadPool).getTaskCount();
System.out.println(MessageFormat.format("当前活跃线程数 :{0}", String.valueOf(threadCount)));
System.out.println(MessageFormat.format("已完成任务 :{0}", String.valueOf(taskCount)));
}
countDownLatch.await();//等到倒数数量执行为0,才会执行下边的代码 自己可以注释掉试下
System.out.println(MessageFormat.format("count :{0}", String.valueOf(count)));
cachedThreadPool.shutdown();//测试完成关闭资源,不然main方法会一直运行
}
/**
* 加上synchronized关键字保证
*/
public static synchronized void add() {
count++;
}
}
package allen.interview.thread.pool.sourceCodeLearn;
import java.text.MessageFormat;
import java.util.concurrent.*;
/**
* java.util.concurrent.Executors 包下的 newFixedThreadPool使用例子
* 该例子场景描述: 简单 做1000次加一操作
*
*/
public class FixedThreadPoolExample {
/**
* 首先贴出使用ThreadPoolExecutor构造函数创建的四个参数我的天啊
* return new ThreadPoolExecutor(nThreads, nThreads,
* 0L, TimeUnit.MILLISECONDS,
* new LinkedBlockingQueue());
* 核心线程数: 必须设定的参数
* 最大线程数量:和核心线程数相同
* 线程最大空闲时间:0
* 空闲时间单位:milliSeconds
* 阻塞队列: LinkedBlockingQueue 一个不限制大小的队列(int 最大值的容量)
*/
private static ExecutorService fixedThreadPool =Executors.newFixedThreadPool(5);
private static int count =0;
public static void main(String[] args) throws InterruptedException {
//信号量控制最大并发量200,把信号量理解成车库门卫就可以了 总共两百个车位可以使用
Semaphore semaphore = new Semaphore(20);
//倒数阀门,类似于3 ,2, 1 芝麻开门的功能, 只有这1000个线程每个都报数了(也就是执行countDown方法)
CountDownLatch countDownLatch = new CountDownLatch(200);
for (int i = 0; i < 200; i++) {
//!!!!重点来了,向我们的线程池提交一个Runnable的run方法,首先看线程池中有没有空闲的已创建的线程
//如果有使用现成的线程, 如果没有则创建新的线程来执行
fixedThreadPool.submit(() -> {
try {
semaphore.acquire();//获取到信号量才可以进行加1操作
Thread.sleep(80);//模拟一个耗时操作,世界上最难的事是装作自己很忙的样子
add();
semaphore.release();//执行完释放信号量
countDownLatch.countDown();//不要忘了报数
} catch (InterruptedException e) {
e.printStackTrace();
}
});
int threadCount = ((ThreadPoolExecutor) fixedThreadPool).getActiveCount();
long taskCount = ((ThreadPoolExecutor) fixedThreadPool).getTaskCount();
System.out.println(MessageFormat.format("当前活跃线程数 :{0}", String.valueOf(threadCount)));
System.out.println(MessageFormat.format("已完成任务 :{0}", String.valueOf(taskCount)));
}
countDownLatch.await();//等到倒数数量执行为0,才会执行下边的代码 自己可以注释掉试下
System.out.println(MessageFormat.format("count :{0}", String.valueOf(count)));
fixedThreadPool.shutdown();//测试完成关闭资源,不然main方法会一直运行
}
/**
* 加上synchronized关键字保证
*/
public static synchronized void add() {
count++;
}
}
package allen.interview.thread.pool.sourceCodeLearn;
import java.text.MessageFormat;
import java.util.concurrent.*;
/**
* java.util.concurrent.Executors 包下的 newSingleThreadExecutor 创建线程池使用的例子
* 该例子场景描述: 简单 做1000次加一操作
*
*/
public class SingleThreadPoolExample {
/**
* 首先贴出使用ThreadPoolExecutor构造函数创建的四个参数我的天啊
* return new ThreadPoolExecutor(1, 1,
* 0L, TimeUnit.MILLISECONDS,
* new LinkedBlockingQueue());
* 核心线程数: 1
* 最大线程数量:1
* 线程最大空闲时间:0
* 空闲时间单位:milliSeconds
* 阻塞队列: LinkedBlockingQueue 一个不限制大小的队列(int 最大值的容量)
*/
private static ExecutorService singleThreadPool=Executors.newSingleThreadExecutor();
private static int count =0;
public static void main(String[] args) throws InterruptedException {
//信号量控制最大并发量200,把信号量理解成车库门卫就可以了 总共两百个车位可以使用
Semaphore semaphore = new Semaphore(1);
//倒数阀门,类似于3 ,2, 1 芝麻开门的功能, 只有这100个线程每个都报数了(也就是执行countDown方法)
CountDownLatch countDownLatch = new CountDownLatch(100);
for (int i = 0; i < 100; i++) {
//提交线程,只有一个线程在执行,所以,其他提交的任务都在队列中等待,线程去处理
singleThreadPool.submit(() -> {
try {
semaphore.acquire();//获取到信号量才可以进行加1操作
Thread.sleep(8);//模拟一个耗时操作,世界上最难的事是装作自己很忙的样子
add();
semaphore.release();//执行完释放信号量
countDownLatch.countDown();//不要忘了报数
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
countDownLatch.await();//等到倒数数量执行为0,才会执行下边的代码 自己可以注释掉试下
System.out.println(MessageFormat.format("count :{0}", String.valueOf(count)));
singleThreadPool.shutdown();//测试完成关闭资源,不然main方法会一直运行
}
/**
* 加上synchronized关键字保证
*/
public static synchronized void add() {
count++;
}
}
package allen.interview.thread.pool.sourceCodeLearn;
import java.text.MessageFormat;
import java.util.concurrent.*;
/**
* java.util.concurrent.Executors 包下的 newScheduledThreadPool 创建线程池使用的例子
* 该例子场景描述: 简单 做1000次加一操作
*
*/
public class ScheduledThreadPoolExample {
/**
* 首先贴出使用ThreadPoolExecutor构造函数创建的四个参数我的天啊
* return new ThreadPoolExecutor(coreNum, 1,
* 0L, TimeUnit.MILLISECONDS,
* new LinkedBlockingQueue());
* 核心线程数: coreNum
* 最大线程数量:int 最大值
* 线程最大空闲时间:0
* 空闲时间单位:milliSeconds
* 阻塞队列: DelayedWorkQueue
*/
private static ScheduledExecutorService scheduledExecutorService=Executors.newScheduledThreadPool(5);
private static int count =0;
public static void main(String[] args) throws InterruptedException {
//信号量控制最大并发量200,把信号量理解成车库门卫就可以了 总共两百个车位可以使用
Semaphore semaphore = new Semaphore(1);
//倒数阀门,类似于3 ,2, 1 芝麻开门的功能, 只有这100个线程每个都报数了(也就是执行countDown方法)
CountDownLatch countDownLatch = new CountDownLatch(100);
for (int i = 0; i < 100; i++) {
//提交线程,只有一个线程在执行,所以,其他提交的任务都在队列中等待,线程去处理
scheduledExecutorService.schedule(() -> {
try {
semaphore.acquire();//获取到信号量才可以进行加1操作
Thread.sleep(8);//模拟一个耗时操作,世界上最难的事是装作自己很忙的样子
add();
semaphore.release();//执行完释放信号量
countDownLatch.countDown();//不要忘了报数
} catch (InterruptedException e) {
e.printStackTrace();
}
},3,TimeUnit.SECONDS) ;
}
countDownLatch.await();//等到倒数数量执行为0,才会执行下边的代码 自己可以注释掉试下
System.out.println(MessageFormat.format("count :{0}", String.valueOf(count)));
scheduledExecutorService.shutdown();//测试完成关闭资源,不然main方法会一直运行
}
/**
* 加上synchronized关键字保证
*/
public static synchronized void add() {
count++;
}
}
重复一下新任务进入时线程池的执行策略:
如果运行的线程少于corePoolSize,则 Executor始终首选添加新的线程,而不进行排队。(如果当前运行的线程小于corePoolSize,则任务根本不会存入queue中,而是直接运行)
如果运行的线程大于等于 corePoolSize,则 Executor始终首选将请求加入队列,而不添加新的线程。
如果无法将请求加入队列,则创建新的线程,除非创建此线程超出 maximumPoolSize,在这种情况下,任务将被拒绝。
主要有3种类型的BlockingQueue:
无界队列
队列大小无限制,常用的为无界的LinkedBlockingQueue,使用该队列做为阻塞队列时要尤其当心,当任务耗时较长时可能会导致大量新任务在队列中堆积最终导致OOM。阅读代码发现,Executors.newFixedThreadPool 采用就是 LinkedBlockingQueue,而楼主踩到的就是这个坑,当QPS很高,发送数据很大,大量的任务被添加到这个无界LinkedBlockingQueue 中,导致cpu和内存飙升服务器挂掉。
有界队列
常用的有两类,一类是遵循FIFO原则的队列如ArrayBlockingQueue与有界的LinkedBlockingQueue,另一类是优先级队列如PriorityBlockingQueue。PriorityBlockingQueue中的优先级由任务的Comparator决定。
使用有界队列时队列大小需和线程池大小互相配合,线程池较小有界队列较大时可减少内存消耗,降低cpu使用率和上下文切换,但是可能会限制系统吞吐量。
在我们的修复方案中,选择的就是这个类型的队列,虽然会有部分任务被丢失,但是我们线上是排序日志搜集任务,所以对部分对丢失是可以容忍的。
同步移交队列
如果不希望任务在队列中等待而是希望将任务直接移交给工作线程,可使用SynchronousQueue作为等待队列。SynchronousQueue不是一个真正的队列,而是一种线程之间移交的机制。要将一个元素放入SynchronousQueue中,必须有另一个线程正在等待接收这个元素。只有在使用无界线程池或者有饱和策略时才建议使用该队列。