java中的线程池

线程池

引言

上一篇介绍了并发容器,它在线程池中发挥着重要作用,本篇将介绍java中的线程池。

Executor,ExecutorService,AbstractExecutorService

Executor是一个接口,只定义一个方法excute,参数是Runnable。这个接口体现了定义与运行的分开,已经告诉了你任务,具体要怎么运行由你自己定义。

public interface Executor {

    /**
     * Executes the given command at some time in the future.  The command
     * may execute in a new thread, in a pooled thread, or in the calling
     * thread, at the discretion of the {@code Executor} implementation.
     *
     * @param command the runnable task
     * @throws RejectedExecutionException if this task cannot be
     * accepted for execution
     * @throws NullPointerException if command is null
     */
    void execute(Runnable command);
}

ExecutorService也是一个接口,它实现了Executor,完整了整个任务执行器的生命周期。

以下是它定义的一些方法

void shutdown();//结束
List<Runnable> shutdownNow();//马上结束
boolean isShutdown();//是否结束了
boolean isTerminated();//是不是整体都执行完了
boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException;//等着结束,等多长时间,时间到了还不结束的话他就返回false

AbstractExecutorService对ExecutorService的一些接口进行了实现,是一个抽象类,是为各种线程池做准备的。但其没有实现excute方法,对其各种不同的实现,分化出了各种不同的线程池。

两种类型线程池ThreadPoolExecutor和FixedThreadPool

ThreadPoolExecutor和ForkJoinPool都是继承于AbstractExecutorService,是两种不同的线程池。可以理解为ThreadPoolExecutor中每个线程取一个任务,就各自运行了。ForkJoinPool可以将同一个任务分块,然后各自运行完后再把结果合起来。所以两种线程池能干的事是不一样的。

手动定义线程池

线程池的方法有很多种构造方法,这里介绍一个最常见的。

 ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 4,
                60, TimeUnit.SECONDS,
                new ArrayBlockingQueue<Runnable>(4),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.CallerRunsPolicy());   

如上new 出ThreadPool后,创建任务,调用前面的提到的excute即可。

这个线程池有七个参数,这里介绍下这七个参数。

  • 第一个参数corePoolSoze核心线程数,最开始的时候是有这个线程池里面是有一定的核心线程数的;

  • 第二个叫maximumPoolSize最大线程数,线程数不够了,能扩展到最大线程是多少;

  • 第三个keepAliveTime生存时间,意思是这个线程有很长时间没干活了请你把它归还给操作系统;

  • 第四个TimeUnit.SECONDS生存时间的单位到底是毫秒纳秒还是秒自己去定义;

  • 第五个是任务队列,就是我们上篇讲的BlockingQueue,各种各样的BlockingQueue你都可以往里面扔,我们这用的是ArrayBlockingQueue,参数最多可以装四个任务;

  • 第六个是线程工厂defaultThreadFactory,他返回的是一个enw DefaultThreadFactory,它要去你去实现ThreadFactory的接口,这个接口只有一个方法叫newThread,所以就是产生线程的,可以通过这种方式产生自定义的线程,默认产生的是defaultThreadFactory,而defaultThreadFactory产生线程的时候有几个特点:new出来的时候指定了group制定了线程名字,然后指定的这个线程绝对不是守护线程,设定好你线程的优先级。

    DefaultThreadFactory() {
                SecurityManager s = System.getSecurityManager();
                group = (s != null) ? s.getThreadGroup() :
                                      Thread.currentThread().getThreadGroup();
                namePrefix = "pool-" +
                              poolNumber.getAndIncrement() +
                             "-thread-";
            }
    
            public Thread newThread(Runnable r) {
                Thread t = new Thread(group, r,
                                      namePrefix + threadNumber.getAndIncrement(),
                                      0);
                if (t.isDaemon())
                    // 若是守护线程,设置为false
                    t.setDaemon(false);
                if (t.getPriority() != Thread.NORM_PRIORITY)
                    t.setPriority(Thread.NORM_PRIORITY);
                return t;
            }
    
  • 第七个叫拒绝策略,指的是线程池忙,而且任务队列满这种情况下我们就要执行各种各样的拒绝策略,jdk默认提供了四种拒绝策略,也是可以自定义的。

    1:Abort:抛异常

    2:Discard:扔掉,不抛异常

    3:DiscardOldest:扔掉排队时间最久的

    4:CallerRuns:调用者处理服务

    根据实际的业务,常常会自定义拒绝策略。

JDK默认线程池

JDK给我们提供了一些默认的线程池的实现,这里总结下常见的默认线程池。

SingleThreadPool

这个线程池只有一个线程,保证任务是顺序执行的。用线程池的好处是:1,线程池是有任务队列的2.生命周期管理线程池是能帮你提供的。

可以运行下面方法,查看线程池的特点。

public static void main(String[] args) {
		ExecutorService service = Executors.newSingleThreadExecutor();
		for(int i=0; i<5; i++) {
			final int j = i;
			service.execute(()->{
				System.out.println(j + " " + Thread.currentThread().getName());
			});
		}
	}
CachedPool

这个线程池用的任务队列是synchronousQueue,所以来一个任务就要马上执行,不然提交任务的线程就阻塞住了。

可以运行下面方法,查看线程池的特点。

public static void main(String[] args) throws InterruptedException {
		ExecutorService service = Executors.newCachedThreadPool();
		System.out.println(service);
		for (int i = 0; i < 2; i++) {
			service.execute(() -> {
				try {
					TimeUnit.MILLISECONDS.sleep(500);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName());
			});
		}
		System.out.println(service);
		TimeUnit.SECONDS.sleep(80);
		System.out.println(service);
	}
FixedThreadPool

固定核心线程和最大线程数。可以submit任务后,多个任务并行运算。

使用时要考虑固定的线程数是否够用。

ScheduledPool

new ScheduledThreadPool的时候返回的是ScheduledThreadPoolExecutor,然后在ScheduledThreadPoolExecutor里面他调用了super,他的super又是ThreadPoolExecutor,它本质上还是ThreadPoolExecutor。

它有一些方法可以对任务定时执行,如scheduleAtFixedRate可以设置间隔多长时间在一个固定的频率上来执行一次这个任务,如下。

 public static void main(String[] args) {
      ScheduledExecutorService service = Executors.newScheduledThreadPool(4);
      service.scheduleAtFixedRate(()->{
         try {
            TimeUnit.MILLISECONDS.sleep(new Random().nextInt(1000));
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
         System.out.println(Thread.currentThread().getName());
      }, 0, 500, TimeUnit.MILLISECONDS);
   }
ForkJoinPool

其和上面说的几个线程池不同之处在于,每个线程都会维护一个任务队列,当一个Thread空下来时,会去别的Thread那拿任务来执行。这样子,如果有一个线程执行一个很重的任务,其他线程空着可以给它分担。封装成的workStealingPoll,实际就是提供了一个更方便的创建接口。

说在最后

本篇介绍了线程池相关的知识。有些知识介绍的比较简略,后续单独介绍ForkJoinPool和解读线程池源码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

平平安安年年

一起学习,一起成长

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值