public static void main(String[] args) throws InterruptedException {
Long start = System.currentTimeMillis();
final Random random = new Random();
final List<Integer> list = new ArrayList<Integer>();
for (int i = 0; i < 100000; i++) {
Thread thread = new Thread() {
@Override
public void run() {
list.add(random.nextInt());
}
};
thread.start();
thread.join();
}
System.out.println("时间:" + (System.currentTimeMillis() - start));
System.out.println("大小:" + list.size());
}
使用线程的方式去执行程序
使用线程池
public static void main(String[] args) throws InterruptedException {
Long start = System.currentTimeMillis();
final Random random = new Random();
final List<Integer> list = new ArrayList<Integer>();
ExecutorService executorService = Executors.newSingleThreadExecutor();
for (int i = 0; i < 100000; i++) {
executorService.execute(new Runnable() {
@Override
public void run() {
list.add(random.nextInt());
}
});
}
executorService.shutdown();
executorService.awaitTermination(1, TimeUnit.DAYS);
System.out.println("时间:"+(System.currentTimeMillis() - start));
System.out.println("大小:"+list.size());
}
由上述对比结果可知,执行相同的逻辑,在时间上使用单个线程耗时更长。
原因
1.使用Thread的程序,每循环一次就创建一个线程,而CPU中是会发生线程的上下文切换的,这个切换会消耗CPU的性能,所以从时间上造成的结果就是使用线程进行程序耗时更长
2.而使用线程池的程序,会对池中的线程进行复用,没有频繁的上下文切换
线程池参数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
- 核心线程数:指的是线程池中核运行的线程数量大小
- 最大线程数:指的是池中允许创建的最大的线程的数量
- 空闲线程存活时间:指的是当线程池中一个线程处于空闲状态,且存活的线程数量大于核心线程数,在超过一定时间之后,这个空闲线程就会被销毁
- 空闲线程存活时间单位:指的是具体的存活时间使用的单位
- 阻塞队列:指的是当新任务被提交,会先进入到队列中,在调度的时候再从队列中取出,JDK中常用的队列有
- SynchronousQueue:不缓存任务的工作队列,即是当有任务进来,若有空闲线程,则直接调度;若没有,则创建新线程进行调度,newCachedThreadPool就是使用这种队列
- LinkedBlockingQuene:基于链表的无界限阻塞队列,容量为 Integer.MAX_VALUE,当有新任务进来,且线程数量达到核心线程数,就会往这个队列中存放任务,但不会创建新线程,JDK中的newFixedThreadPool和newSingleThreadExecutor就是使用该队列
线程池种类
- newCachedThreadPool
- newFixedThreadPool
- newSingleThreadExecutor
- ThreadPoolExecutor(自定义线程池)
比较各个线程池
public class ThreadPoolDemo {
public static void main(String[] args) {
ExecutorService executorService1 = Executors.newCachedThreadPool();//快
ExecutorService executorService2 = Executors.newFixedThreadPool(10);//慢
ExecutorService executorService3 = Executors.newSingleThreadExecutor();//最慢
for (int i = 1; i <= 100; i++) {
executorService1.execute(new MyTask(i));
}
}
}
class MyTask implements Runnable {
int i = 0;
public MyTask(int i) {
this.i = i;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "程序员做第" + i + "个项目");
try {
Thread.sleep(3000L);//业务逻辑
} catch (Exception e) {
e.printStackTrace();
}
}
}
分别使用者三种线程池运行程序
可以得出,executorService1最快,executorService2次之,executorService3最慢
从源码层次分析原因
newFixedThreadPool实现如下
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
} public static ExecutorService newFixedThreadPool(int nThreads) {
newSingleThreadExecutor实现如下
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
对比主要的参数区别
线程池种类/参数设置 | corePoolSize | maximumPoolSize | workQueue |
newCachedThreadPool | 0 | Integer.MAX_VALUE | SynchronousQueue |
newFixedThreadPool | nThreads | nThreads | LinkedBlockingQueue |
newSingleThreadExecutor | 1 | 1 | LinkedBlockingQueue |
由对比结果可知,newCachedThreadPool可创建的线程数量没有上限,且任务来了就可以直接执行,所以它执行的速度最快;而newFixedThreadPool可创建的线程数量随初始化线程池时定下的nThreads决定,且任务进来以后是放在队列中进行等待的,只有池中有空闲的线程才会从队列中取出(FIFO)进行调度;相比第一个线程池,它得等待之前的线程空闲才能执行,所以时间稍慢;而newSingleThreadExecutor,它只有一个线程进行工作,后续的任务只能依靠这一个线程来执行,所以最慢。
自定义线程池
public class ThreadPoolDemo {
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 20,
0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(10));
for (int i = 1; i <= 120; i++) {
threadPoolExecutor.execute(new MyTask(i));
}
}
}
class MyTask implements Runnable {
int i = 0;
public MyTask(int i) {
this.i = i;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "程序员做第" + i + "个项目");
try {
Thread.sleep(3000L);//业务逻辑
} catch (Exception e) {
e.printStackTrace();
}
}
}
执行结果
触发了拒绝策略
分析线程池执行的代码
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
* Proceed in 3 steps:
*
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it shouldn't, by returning false.
*
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*/
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}
执行流程
- 判断工作线程数量是否小于核心线程数,若是,使用新提交的任务创建线程,若创建成功则提交任务执行
- 若大于等于核心线程数,判断线程池是否还属于运行状态、且入参的任务能成功添加到队列,然后进行再次检测,若线程池不是运行状态且入参任务被移除,使用拒绝策略拒绝任务;若不是,再判断当前线程数量是否为0,若是,创建一个线程空跑。
- 若新增线程失败,执行拒绝策略