线程池,顾名思义就是线程的池子,里面有若干线程,它们的任务就是执行提交给线程池的任务,执行完之后不会退出,而是继续等待或执行新任务。
线程池由两部分组成:任务队列和工作线程。
任务队列:保存待执行的任务;
工作线程:循环从任务队列里取任务并执行。
线程池的概念类似于生活中在医院就诊的时候,需要医生给你探查病情,一个办公室里面有三个医生,然后病人就在门口排队,这个办公室就是一个线程池,三个医生就是里面工作线程,门口排的队列就是任务队列。
线程池的优点:
1.可以重用线程,避免线程创建的开销;
2.任务过多时,通过队列避免创建过多的线程,减少资源消耗和竞争,确保任务有序完成。
Java的线程池的实现类是ThreadPoolExecutor;
这个类中的构造方法主要的:
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long KeepAliveTime, TimeUnit unit, BlockingQueue
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long KeepAliveTime, TimeUnit unit, BlockingQueue
上述threadFactory、 handler两个参数一般不需要,第一个构造方法会给出默认值。
1.线程池的大小
上述构造方法中的参数中有四个跟线程池的大小有关系:
corePoolSize:核心线程个数
maximumPoolSize:最大线程个数
KeepAliveTime:线程存活时间
unit:线程存活时间的单位
线程池中的线程是呈动态变化的,但是不管有多少任务,都不会创建比maximumPoolSize大的线程个数。
刚创建一个线程池的时候并不会创建任何线程,一般情况下,有任务到来的时候,如果当前线程个数小于corePoolSize,就会创建一个新线程来执行任务,即使当前其他线程空闲,也会创建新线程。
如果当前线程数大于等于corePoolSize时,就不会立即创建新线程,而是尝试排队,如果当前队列满了或其他原因不能立即入队,会检查当前线程数量是否达到maximumPoolSize,如果未达到才创建新线程。
KeepAliveTime表示当线程池中线程个数大于corePoolSize时额外空闲线程的存活时间。这就意味着一个非核心线程,在空闲等待新任务的时候最长等待KeepAliveTime的时间,时间到了还是没有新任务就会被终止。
2.队列
TheardPoolExecutor要求队列是阻塞队列。
LinkedBlockingQueue:基于链表的阻塞队列,可以指定最大长度,默认无界;
ArrayBlockingQueue:基于数组的有限队列;
PriorityBlockingQueue:基于堆的无限阻塞队列;
SynchronousQueue:没有实际存储空间的同步阻塞队列。(每个操作必须等到操作结束后才执行下一个操作,就意味着只有当有空闲线程的时候才会接受任务);
3.任务拒绝策略
如果当队列有界,且maximumPoolSize有限,则当队列排满,线程个数也达到了最大值,此时新任务来了,会触发线程拒绝策略。
默认情况下会抛出异常,类型为RejectedExecutionException。
ThreadPoolExecutor实现了四种处理方式;
1.AbortPolicy:默认处理方式,抛出异常;
2.DiscardPolicy:忽略新任务;
3.DiscardOldestPolicy:将等待时间最长的任务丢弃,然后排到队尾
4.CallerRunsPolicy:将任务交给提交任务的线程去执行。
4.工厂类
1.为了更方便地创建一些预配置的线程池,Executors提供了一些方法
public static ExecutorService newSingleThreadExecutor();
public static ExecutorService newFixedThreadExecutor(int nThreads);
public static ExecutorService newCachedThreadPool();
1.newSingleThreadExecutor()是相当于返回:
new ThreaPoolExecutor(1,1,0L,TimeUnit.MILLSECONDS,new LinkedBlockingQueue
只使用一个线程,使用无界队列,线程创建后不会超时终止。
2.newFixedThreadPool(int nThreads)是相当于返回:
new ThreaPoolExecutor(nThreads,nThreads,0L,TimeUnit.MILLSECONDS,new LinkedBlockingQueue
使用固定数目的n个线程数目,使用无界队列,线程不会超时终止。
3.newCacheThreadPool()是相当于返回:
new ThreaPoolExecutor(0,Integer.Max_VALUE,60L,TimeUnit.MILLSECONDS,new SynchronousQueue
当线程任务到来时,如果有空闲线程在等待任务,则空闲线程接受任务,否则创建新线程,创建的总个数不受限制,对任一空闲线程,如果60秒内没有新任务就会终止。
5.提交任务
execute(Runnable):用于提交没有返回值的任务,无法判断是否被线程池成功执行;
submit(Runnable/Callable):用于提交有返回值的任务,可以通过返回的Future来判断是否被成功执行。
public class{
public static void main(String[] args) throws {
ExecutorService newcachedThreadPool = Executors.newCachedThreadPool();
for(int i=0;i<10;i++){
int index =i;
newcachedThreadPool.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+",i="+index);
}
});
}
newcachedThreadPool.shutdown();
}
}
上述代码创建了一个没有界限的线程池,并向其中提交了十次任务;
pool-1-thread-1,i=0
pool-1-thread-2,i=1
pool-1-thread-3,i=2
pool-1-thread-5,i=4
pool-1-thread-4,i=3
pool-1-thread-6,i=5
pool-1-thread-8,i=7
pool-1-thread-7,i=6
pool-1-thread-10,i=9
pool-1-thread-9,i=8
执行的结果如上面所示,一个线程池创建了十个线程,每个线程执行自己的任务。
熟悉了线程池,下面接着说定时任务;
TimerTask表示一个定时任务,是一个抽象类,实现了Runnable接口,具体的任务需要继承该类来确定,实现run方法。TImer是一个具体类,它负责定时任务的调度和执行。
public void schedule(TimerTask task,Date time):在指定的时间time运行task任务;
public void schedule(TimerTask task,long delay):在当前时间延迟delay毫秒后运行任务;
public void schedule(TimerTask task, Date firstTime,long period):固定延时重复执行,第一次计划执行时间为firstTime,后一次的计划执行时间为前一次“实际”执行时间加上period;
public void schedule(TimerTask task,long delay,long period):同是固定延时重复执行,第一次执行时间为当前时间加上delay;
public void scheduleAtFixedRate(TimerTask task, Date firstTime,long period):固定频率重复执行,第一次计划执行时间为firstTime,后一次的计划执行时间为前一次“计划执行时间”加上period;
public void scheduleAtFixedRate(TimerTask task,long delay,long period):同是固定频率重复执行,第一次计划执行时间为当前时间加上delay;
对于固定延时,它是基于上次任务的“实际”执行时间来计算的,如果因为某些原因,上次任务延时了,那么本次任务也会延迟。
对于固定频率,会尽量补够运行次数。
public class A{
static class LongRunningTask extends TimerTask{
@Override
public void run() {
try {
Thread.sleep(5000);
}
catch (InterruptedException e) {
}
System.out.println("Long running finished");
}
}
static class FixedDelayTask extends TimerTask{
@Override
public void run() {
System.out.println(System.currentTimeMillis());
}
}
public static void main(String[] args) throws InterruptedException {
Timer timer= new Timer();
timer.schedule(new LongRunningTask(),10);
timer.schedule(new FixedDelayTask(),100,1000);
}
}
上述代码可以看出执行了两个定时任务,第一个耗时五秒,只执行一次,第二个是重复执行,一秒一次,第一个先运行。
public class B{
static class LongRunningTask extends TimerTask{
@Override
public void run() {
try {
Thread.sleep(5000);
}
catch (InterruptedException e) {
}
System.out.println("Long running finished");
}
}
static class FixedDelayTask extends TimerTask{
@Override
public void run() {
System.out.println(System.currentTimeMillis());
}
}
public static void main(String[] args) throws InterruptedException {
Timer timer= new Timer();
timer.schedule(new LongRunningTask(),10);
timer.scheduleAtFixedRate(new FixedDelayTask(),100,1000);
}
}
这同样执行两个任务,第二个任务会把未执行的任务补起来,一下子运行五次。
原因是一个Timer对象只有一个Timer线程,所以上面的例子会被延迟,Timer线程主体是一个死循环,当队列中有任务且计划执行时间小于等于当前时间,就执行它。如果队列中没有任务或第一个任务延时还没到,就睡眠,如果睡眠过程中队列添加了新任务且新任务是第一个任务,Timer就会被唤醒。
下次任务的计划是在执行当前任务之前就做出的,对于固定延时任务,延时相对的是任务执行前的当前时间;对于固定频率的任务,延时相对的是最先的计划。
public class Main{
static class TaskA extends TimerTask{
@Override
public void run() {
System.out.println("task A");
}
}
static class TaskB extends TimerTask{
@Override
public void run() {
System.out.println("task B");
throw new RuntimeException();
}
}
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new TaskA(),1,1000);
timer.schedule(new TaskB(),2000,1000);
}
}
task A
task A
task B
Exception in thread "Timer-0" java.lang.RuntimeException
at Demo01.jdbc01$TaskB.run(jdbc01.java:55)
at java.util.TimerThread.mainLoop(Timer.java:555)
at java.util.TimerThread.run(Timer.java:505)
上面就是运行的结果。
下面再来看看线程调度池:
ScheduledThreadPoolExecutor,这是线程池ThreadPoolExecutor的子类,是基于线程池实现的。
为了方便创建ScheduledThreadPoolExecutor,Executors提供了一些方法:
public static ScheduledExecutorService newSingleThreadScheduledExecutor():执行单线程的定时任务。
public static ScheduledExecutorService newSingleThreadScheduledExecutor(int corePoolSize):执行多线程的定时任务。
public class Main{
static class LongRunningTask extends TimerTask{
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
}
System.out.println("Long running finished");
}
}
static class FixedDelayTask extends TimerTask{
@Override
public void run() {
System.out.println(System.currentTimeMillis());
}
}
public static void main(String[] args) {
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
scheduledExecutorService.schedule(new LongRunningTask(),10,TimeUnit.MILLISECONDS);
scheduledExecutorService.scheduleWithFixedDelay(new FixedDelayTask(),100,1000,TimeUnit.MILLISECONDS);
}
}
从上面的代码就可以明显的看出,第二个任务并没有因为第一个任务没有结束而不开始。
ScheduledThreadPoolExecutor的实现思路与Timer大致相同,都是基于堆的优先级队列,保存定时执行的任务。
但是不同的是:
1.ScheduledThreadPoolExecutor是基于线程池实现的,可同时执行多个任务。
2.它在任务执行后才设置下一次执行任务的时间;
3.任务执行线程会捕获任务执行线程中的所有异常,一个任务出错不会影响其他定时任务,不过捕获的任务不可以再继续执行下去。
好了,下次再总结,手敲代码去了。