线程(4)——线程池

目录

 

1.1JDK对线程池的支持

1.2核心线程池的内部实现

ThreadPoolExecutor的核心调度代码

1.3拒绝策略


1.1JDK对线程池的支持

为了更好的能够控制多线程,JDK提供了一套Executor框架,其本质是一个线程池。

                                                                                       上面是Executor的框架图。

如图Executors类扮演着线程池工厂的角色,通过Executors可以取得一个拥有特定功能的线程池。从图中看到ThreadPoolExecutor类实现了Executor接口,因此通过这个接口任何Runnable的对象都可以调用。

Executor提供了以下工厂方法:

  • newFixedThreadPool():该方法返回一个固定线程数量的线程池。线程池里若有空闲线程则立即执行,否则暂存在一个任务队列中,等待空闲线程。
  • newSingleThreadExecutor():该方法返回了一个只有一个线程的线程池。
  • newCachedThreadPool():该方法返回了一个可根据实际情况调整线程数量的线程。线程池里若有空闲线程则立即执行,否则会创建一个新的线程。
  • newSingleThreadScheduleExecutor():该方法返回一个ScheduleExecutorService对象,线程池大小为1。ScheduleExecutorService接口在ExecutorService接口之上,扩展了在给定时间执行某任务的功能。
  • newScheduleThreadPool():该方法也返回一个ScheduleExecutorService接口,但该线程池可以指定线程池数量。

ScheduleExecutorService对象主要的一些方法:

  • schedule():会在给定时间,对任务进行一次调度。
  • scheduleAtFixedRate():创建一个周期任务。任务开始于给定初始延时。后续的任务按照给定的周期执行:后续第一个任务会在initialDelay+period时执行,后续第二个任务会在initialDelay+2*period时执行,以此类推。
  • scheduleWithFixedDelay():创建并执行一个周期性任务。任务开始于初始延时时间,后续任务将会按照给定的延时进行,即上一个任务结束到下一个任务开始的时间差进行。
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ScheduleExecutorServoceDemo {
	public static void main(String[] args) {
		ScheduledExecutorService ses = Executors.newScheduledThreadPool(10);
		ses.scheduleAtFixedRate(new Runnable() {
			public void run() {
				// TODO Auto-generated method stub
				try {
					Thread.sleep(1000);
					System.out.println(System.currentTimeMillis()/1000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				
			}
		}, 1, 2, TimeUnit.SECONDS);
	}
}

 运行结果:

1537947034
1537947036
1537947038
1537947040
1537947042

可以看到这里时间间隔是2秒。

如果执行时间超过调度时间会发生什么事情。将上述Thread.sleep(1000);改为

Thread.sleep(3000);

运行结果:

1537947606
1537947609
1537947612
1537947615

发现运行任务的执行周期为3秒。也就是说周期小于执行时间,那么任务就会在上一个任务结束后立即被调用。

如果采用scheduleWithFixedDelay(),执行3秒,调度两秒,那么时间间隔为5秒。

1.2核心线程池的内部实现

首先了解一下ThreadPoolExecutor最重要的构造函数

 public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)
  • corePoolSize:指定了线程池中的线程数量。
  • maximumPoolSize:制定了线程池中最大线程的数量。
  • keepAliveTime:当线程池数量超过corePoolSize时,多余的空闲线程的存活时间。即,超过corePoolSize的空闲线程,在多长时间内被销毁。
  • unit:keepAliveTime的单位。
  • workQueue:任务队列,被提交但尚未被执行的任务。

workQueue,是一个BlockingQueue接口的对象,仅用于存放Runnable对象。在ThreadPoolExecutor的构造函数中可食用以下几种BlockingQueue。

  1. 直接提交的队列:该功能由SynchronousQueue对象提供。SynchronousQueue是一个特殊的BlockingQueue。它没有容量,每一个插入操作都要等待一个相应的删除操作,反之每一个删除操作都要等待一个对应的插入操作。使用SynchronousQueue,提交的任务不会被真实保存,而总是将新任务交给线程执行,如果没有空闲线程,则创建新线程,如果线程数量达到最大值,则执行拒绝策略。
  2. 有界的任务队列:该队列可由ArrayBlockingQueue实现。它的构造函数必须带一个容量参数。表示该队列最大容量。有界队列当在任务线程满的时候才能将线程数提升到corePoolSize以上。
  3. 无界的任务队列:无界的任务队列可通过LinkedBlockingQueue类实现。与有界任务队列相比,除非系统资源耗尽,否则不会加入失败。
  4. 优先任务队列:优先任务队列是带有执行优先级的任务队列。他通过PriorityBlockingQueue实现,可以控制任务的执行先后顺序。它是一个特殊的无界队列。
  • threadFactory:用于创建线程,一般用默认的即可。

  • handler:拒绝策略。当任务太多来不及处理,如何拒绝任务。

ThreadPoolExecutor的核心调度代码

 public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        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);
    }

代码第五行workerCountOf()函数取得了当前线程池的线程总数。当前线程总数小于corePoolSize时,将会通过addWorker()方法直接调度执行。否则在第十行代码处(workQueue.offer())进入等待队列。如果进入等待队列失败,则会执行第17行,将任务直接交给线程池。如果当前线程数已达到maximumPoolSize,则提交失败,就执行18行的拒绝策略。

                                                         上图是ThreadPoolExecutor的任务调度逻辑

1.3拒绝策略

JDK内置的拒绝策略如下。

  • AbortPolicy策略:该策略会直接抛出异常,组织系统工作。
  • CallerRunsPolicy策略:只要线程池关闭,该策略直接在调用者线程中,运行当前被丢弃的任务。显然这样做不会真的丢弃任务,但是任务提交的性能会急剧下降。
  • DiscardOldestPolicy策略:该策略丢弃最老的一个请求,也就是即将被执行的任务,并尝试再次提交当前任务。
  • DiscardPolicy策略:该策略默默丢弃无法处理的任务,不予任何处理。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值