线程池:在我的理解来看,就是一个保存了一定数量线程的空间,系统提前申请好空间和资源,在线程池销毁之前一直等待着被调用。
在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内存资源或者其它更多资源。在java的并发开发中,如果说并发的线程数量很多,并且每一个线程都是执行很短的任务就结束的话,这样频繁的创建和销毁线程就会大大的降低系统的效率。
如此需求,就有了以下的解决办法——线程池:执行完一个任务并不销毁,而是进入休眠状态来等待事件的发生。
答:因为使用线程池的好处是减少在创建和销毁线程上所消耗的时间和系统资源,解决资源不足的问题,如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者过渡切换问题。至于显示创建Thread的两种方式,参见博客。
需要大量的线程来完成任务,且完成任务的时间较短
。例如Web服务器完成网页请求,因为单个任务小且任务量非常的巨大,非常适合线程池技术。可以想一下火爆网站的瞬间访问量。诚然,服务器的处理速度是非常重要的,但是线程池的技术也是重中之重。但是时间较长的任务请求线程池的优点就不明显了。因为这种操作花费的时间和线程的创建销毁时间相比多了很多。
对性能要求苛刻的应用
,比如要求服务器迅速响应客户请求。
接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用
。突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,并出现"OutOfMemory"的错误。
这是一个有指定的线程数的线程池,有核心的线程,里面有固定的线程数量,响应的速度快。正规的并发线程,多用于服务器。固定的线程数由系统资源设置。
//指定线程数
public static ExecutorService newFixedThreadPool(int threads)
{
return newFixedThreadPool(threads,threads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
}
核心线程是没有超时机制的,队列大小没有限制,除非线程池关闭了核心线程才会被回收。
只有非核心线程,最大线程数很大(Int.Max(values)),可以理解为无界,它会为每一个任务添加一个新的线程,这边有一个超时机制,当空闲的线程超过60s内没有用到的话,就会被回收。缺点就是没有考虑到系统的实际内存大小。
这种类型的线程池特点是:
//无参数
public static ExecutorService newCachedThreadPool()
{
return newFixedThreadPool(threads,Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
}
只有一个核心线程,将任务通过一个缓存队列持续放到线程中去,完成一个则下一个进入。没有并发性,处理速度慢。会发生阻塞和拥塞。
这个线程池就厉害了,是唯一一个有延迟执行和周期重复执行的线程池。它的核心线程池固定,非核心线程的数量没有限制,但是闲置时会立即会被回收。
/**
* 创建固定大小的线程池
*/
public static void createFixedThreadPool() {
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
final CountDownLatch countDownLatch = new CountDownLatch(5);
for (int i = 0; i < 5; i++) {
final int currentIndex = i;
fixedThreadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + ", currentIndex is : " + currentIndex);
countDownLatch.countDown();
});
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("全部线程执行完毕");
}
打印结果:
pool-1-thread-4, currentIndex is : 3
pool-1-thread-5, currentIndex is : 4
pool-1-thread-2, currentIndex is : 1
pool-1-thread-1, currentIndex is : 0
pool-1-thread-3, currentIndex is : 2
全部线程执行完毕
答:其实不直接使用工具类的目的只有一个,那就是可以明确的让我们知道线程池的运行规则,避免使用工具类的包装而不够直观内部机制而导致潜在的问题
。譬如使用 Executors 的 FixedThreadPool 和 SingleThreadPool 创建线程池的原理都允许请求的队列长度为 Integer 的最大值,这样的话可能会堆积大量的请求导致OOM(程序申请内存过大,虚拟机无法满足我们,然后自杀了。)
;所以推荐直接通过明确的构造参数创建线程池,这样就相当与时刻提醒自己的线程池特性是什么。
java.uitl.concurrent.ThreadPoolExecutor类是线程池中最核心的一个类,因此如果要透彻地了解Java中的线程池,必须先了解这个类。下面我们来看一下ThreadPoolExecutor类的具体实现源码。
在ThreadPoolExecutor类中提供了四个构造方法:
public class ThreadPoolExecutor extends AbstractExecutorService {
.....
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
...
}
从上面的代码可以得知,ThreadPoolExecutor继承了AbstractExecutorService类,并提供了四个构造器,事实上,通过观察每个构造器的源码具体实现,发现前面三个构造器都是调用的第四个构造器进行的初始化工作。
下面解释下一下构造器中各个参数的含义:
TimeUnit.DAYS; //天
TimeUnit.HOURS; //小时
TimeUnit.MINUTES; //分钟
TimeUnit.SECONDS; //秒
TimeUnit.MILLISECONDS; //毫秒
TimeUnit.MICROSECONDS; //微妙
TimeUnit.NANOSECONDS; //纳秒
ArrayBlockingQueue;
LinkedBlockingQueue;
SynchronousQueue;
ArrayBlockingQueue和PriorityBlockingQueue使用较少,一般使用LinkedBlockingQueue和Synchronous。线程池的排队策略与BlockingQueue有关。
threadFactory:线程工厂,主要用来创建线程;
handler:表示当拒绝处理任务时的策略,有以下四种取值:
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
/**
* 使用ThreadPoolExecutor创建线程池
*/
public void createThreadPoolExecutor() {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 10L, TimeUnit.SECONDS,
new LinkedBlockingDeque<>(1000),
new ThreadPoolExecutor.AbortPolicy());
final CountDownLatch countDownLatch = new CountDownLatch(8);
for (int i = 0; i < 8; i++) {
final int currentIndex = i;
System.out.println("提交第" + i + "个线程");
threadPoolExecutor.execute(() -> {
System.out.println(Thread.currentThread().getName() + ", currentIndex is : " + currentIndex);
countDownLatch.countDown();
});
}
System.out.println("全部提交完毕");
try {
System.out.println("准备等待线程池任务执行完毕");
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("全部线程执行完毕");
}
提交第0个线程
提交第1个线程
提交第2个线程
提交第3个线程
提交第4个线程
pool-1-thread-2, currentIndex is : 1
提交第5个线程
pool-1-thread-4, currentIndex is : 3
pool-1-thread-3, currentIndex is : 2
pool-1-thread-1, currentIndex is : 0
pool-1-thread-3, currentIndex is : 5
pool-1-thread-5, currentIndex is : 4
提交第6个线程
提交第7个线程
pool-1-thread-2, currentIndex is : 6
pool-1-thread-2, currentIndex is : 7
全部提交完毕
准备等待线程池任务执行完毕
全部线程执行完毕
还有个问题也遇见了好几次,是线程中的用户线程和守护线程的区别。参见博客