线程池,定时任务,线程调度池

线程池,顾名思义就是线程的池子,里面有若干线程,它们的任务就是执行提交给线程池的任务,执行完之后不会退出,而是继续等待或执行新任务。

线程池由两部分组成:任务队列工作线程

任务队列:保存待执行的任务;

工作线程:循环从任务队列里取任务并执行。

 线程池的概念类似于生活中在医院就诊的时候,需要医生给你探查病情,一个办公室里面有三个医生,然后病人就在门口排队,这个办公室就是一个线程池,三个医生就是里面工作线程,门口排的队列就是任务队列。

线程池的优点:

1.可以重用线程,避免线程创建的开销;

2.任务过多时,通过队列避免创建过多的线程,减少资源消耗和竞争,确保任务有序完成。

Java的线程池的实现类是ThreadPoolExecutor;

这个类中的构造方法主要的:

public  ThreadPoolExecutor(int corePoolSize,int  maximumPoolSize,long  KeepAliveTime,   TimeUnit  unit,  BlockingQueue<Runnable> workQueue)

扫描二维码关注公众号,回复: 3735401 查看本文章

public  ThreadPoolExecutor(int corePoolSize,int  maximumPoolSize,long  KeepAliveTime,   TimeUnit  unit,  BlockingQueue<Runnable> workQueue,ThreadFactory  threadFactory,RejectedExecutionHandler  handler)

上述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 newCached

ThreadPool();

1.newSingleThreadExecutor()是相当于返回:

 new ThreaPoolExecutor(1,1,0L,TimeUnit.MILLSECONDS,new LinkedBlockingQueue<Runnable>());

只使用一个线程,使用无界队列,线程创建后不会超时终止。

2.newFixedThreadExecuto(int nThreads)是相当于返回:

 new ThreaPoolExecutor(nThreads,nThreads,0L,TimeUnit.MILLSECONDS,new LinkedBlockingQueue<Runnable>());

使用固定数目的n个线程数目,使用无界队列,线程不会超时终止。

3.newCacheThreadPool()是相当于返回:

 new ThreaPoolExecutor(0,Integer.Max_VALUE,60L,TimeUnit.MILLSECONDS,new SynchronousQueue<Runnable>());

当线程任务到来时,如果有空闲线程在等待任务,则空闲线程接受任务,否则创建新线程,创建的总个数不受限制,对任一空闲线程,如果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.任务执行线程会捕获任务执行线程中的所有异常,一个任务出错不会影响其他定时任务,不过捕获的任务不可以再继续执行下去。

好了,下次再总结,手敲代码去了。

猜你喜欢

转载自blog.csdn.net/volatile_0524/article/details/82814418