java基础之线程池

线程池: 提供一个线程队列,队列中保存着所有等待状态的线程。避免了创建与销毁的额外开销,提高了响应的速度

 在我们的日常开发中,难免会使用到线程,部分还会用到多线程并发问题。我们知道,线程的创建和释放,需要占用不小的内存和资源。如果每次需要使用线程时,都new 一个Thread的话,难免会造成资源的浪费,而且可以无限制创建,之间相互竞争,会导致过多占用系统资源导致系统瘫痪。不利于扩展,比如如定时执行、定期执行、线程中断,所以很有必要了解下ExecutorService的使用。

        ExecutorService是Java提供的线程池,也就是说,每次我们需要使用线程的时候,可以通过ExecutorService获得线程。它可以有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞,同时提供定时执行、定期执行、单线程、并发数控制等功能,也不用使用TimerTask了。

1.ExecutorService的创建方式

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

所有线程池最终都是通过这个方法来创建的。

corePoolSize : 核心线程数,一旦创建将不会再释放。如果创建的线程数还没有达到指定的核心线程数量,将会继续创建新的核心线程,直到达到最大核心线程数后,核心线程数将不在增加;如果没有空闲的核心线程,同时又未达到最大线程数,则将继续创建非核心线程;如果核心线程数等于最大线程数,则当核心线程都处于激活状态时,任务将被挂起,等待空闲线程来执行。

maximumPoolSize : 最大线程数,允许创建的最大线程数量。如果最大线程数等于核心线程数,则无法创建非核心线程;如果非核心线程处于空闲时,超过设置的空闲时间,则将被回收,释放占用的资源。

keepAliveTime : 也就是当线程空闲时,所允许保存的最大时间,超过这个时间,线程将被释放销毁,但只针对于非核心线程。

unit : 时间单位,TimeUnit.SECONDS等。

workQueue : 任务队列,存储暂时无法执行的任务,等待空闲线程来执行任务。

threadFactory :  线程工程,用于创建线程。

handler : 当线程边界和队列容量已经达到最大时,用于处理阻塞时的程序

2.线程池的类型

2.1 可缓存线程池

ExecutorService cachePool = Executors.newCachedThreadPool();

   看看它的具体创建方式:

public static ExecutorService newCachedThreadPool() {        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,                                      60L, TimeUnit.SECONDS,                                      new SynchronousQueue<Runnable>());    }

    通过它的创建方式可以知道,创建的都是非核心线程,而且最大线程数为Interge的最大值,空闲线程存活时间是1分钟。如果有大量耗时的任务,则不适该创建方式。它只适用于生命周期短的任务。

2.2 单线程池

ExecutorService singlePool = Executors.newSingleThreadExecutor();

顾名思义,也就是创建一个核心线程:

public static ExecutorService newSingleThreadExecutor() {        return new FinalizableDelegatedExecutorService            (new ThreadPoolExecutor(1, 1,                                    0L, TimeUnit.MILLISECONDS,                                    new LinkedBlockingQueue<Runnable>()));    }

只用一个线程来执行任务,保证任务按FIFO顺序一个个执行。

2.3 固定线程数线程池

Executors.newFixedThreadPool(3);

public static ExecutorService newFixedThreadPool(int nThreads) {        return new ThreadPoolExecutor(nThreads, nThreads,                                      0L, TimeUnit.MILLISECONDS,                                      new LinkedBlockingQueue<Runnable>());    }

也就是创建固定数量的可复用的线程数,来执行任务。当线程数达到最大核心线程数,则加入队列等待有空闲线程时再执行。

2.4 固定线程数,支持定时和周期性任务

ExecutorService scheduledPool = Executors.newScheduledThreadPool(5);

public ScheduledThreadPoolExecutor(int corePoolSize) {    super(corePoolSize, Integer.MAX_VALUE,          DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,          new DelayedWorkQueue());}

可用于替代handler.postDelay和Timer定时器等延时和周期性任务。

public ScheduledFuture<?> schedule(Runnable command,                                       long delay, TimeUnit unit);

public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,                                                  long initialDelay,                                                  long period,                                                  TimeUnit unit);

public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,                                                     long initialDelay,                                                     long delay,                                                     TimeUnit unit);

scheduleAtFixedRate和sheduleWithFixedDelay有什么不同呢?

scheduleAtFixedRate:创建并执行一个在给定初始延迟后的定期操作,也就是将在 initialDelay 后开始执行,然后在initialDelay+period 后下一个任务执行,接着在 initialDelay + 2 * period 后执行,依此类推 ,也就是只在第一次任务执行时有延时。

sheduleWithFixedDelay:创建并执行一个在给定初始延迟后首次启用的定期操作,随后,在每一次执行终止和下一次执行开始之间都存在给定的延迟,即总时间是(initialDelay +  period)*n

2.4 手动创建线程池

private ExecutorService pool = new ThreadPoolExecutor(3, 10,            10L, TimeUnit.SECONDS,            new LinkedBlockingQueue<Runnable>(512), Executors.defaultThreadFactory(),            new ThreadPoolExecutor.AbortPolicy());

可以根据自己的需求创建指定的核心线程数和总线程数。

3. 如何终止一个周期性任务呢?

shundown 方法

常见区别

ExecutorService的submit和execute

ExecuteService代表的是Executors创建的线程池
submit提交的是Callable方法,返回Future,说明submit是有返回值的
execute执行的是Runnable方法,没有返回值
所以submit和execute的区别是提交的方法和是否有返回值

ExecutorService的shutdown,shutdownNow,awaitTermination

flume中的关闭源码

 

  public void stop() {
    LOGGER.info("Configuration provider stopping");

    executorService.shutdown();
    try {
      if (!executorService.awaitTermination(500, TimeUnit.MILLISECONDS)) {
        LOGGER.debug("File watcher has not terminated. Forcing shutdown of executor.");
        executorService.shutdownNow();
        while (!executorService.awaitTermination(500, TimeUnit.MILLISECONDS)) {
          LOGGER.debug("Waiting for file watcher to terminate");
        }
      }
    } catch (InterruptedException e) {
      LOGGER.debug("Interrupted while waiting for file watcher to terminate");
      Thread.currentThread().interrupt();
    }
    lifecycleState = LifecycleState.STOP;
    LOGGER.debug("Configuration provider stopped");
  }

shutdown方法:平滑的关闭ExecutorService,当此方法被调用时,ExecutorService停止接收新的任务并且等待已经提交的任务(包含提交正在执行和提交未执行)执行完成。当所有提交任务执行完毕,线程池即被关闭。
awaitTermination方法:接收人timeout和TimeUnit两个参数,用于设定超时时间及单位。当等待超过设定时间时,会监测ExecutorService是否已经关闭,若关闭则返回true,否则返回false。一般情况下会和shutdown方法组合使用。

  • 场景
    应用场景为线程池的有效执行时间为20S,20S之后不管子任务有没有执行完毕,都要关闭线程池。代码如下

 

ExecutorService es = Executors.newFixedThreadPool(10);        
es.execute(new Thread());//执行子线程任务     
   try {    
    es.shutdown();  
        if(!es.awaitTermination(20,TimeUnit.SECONDS)){//20S 
       System.out.println(" 到达指定时间,还有线程没执行完,不再等待,关闭线程池!");      
   es.shutdownNow();    
    }
    } catch (Throwable e) {     // TODO Auto-generated catch block  
    es.shutdownNow();   
    e.printStackTrace();
    }

awaitTermination方法调用会被阻塞,直到所有任务执行完毕并且shutdown请求被调用,或者参数中定义的timeout时间到达或者当前线程被打断,这几种情况任意一个发生了就会导致该方法的执行。
当我们调用pool.awaitTermination时,首先该方法会被阻塞,这时会执行子线程中的任务,子线程执行完毕后该方法仍然会被阻塞,因为shutdown()方法还未被调用,而代码中将shutdown的请求放在了awaitTermination之后,这样就导致了只有awaitTermination方法执行完毕后才会执行shutdown请求,这样就造成了死锁。
shutdown的请求一定要放在awaitTermination之前

ExecuteService执行任务的异常处理

https://www.cnblogs.com/langtianya/p/4520373.html

 

下面这段代码执行的结果是什么?
executorService.submit(() -> {
    System.out.println(1 / 0);
});
我被它坑过无数回了:它什么也不会输出。没有任何的java.lang.ArithmeticException: / by zero的征兆,啥也没有。线程池会把这个异常吞掉,就像什么也没发生过一样。如果是你自己创建的java.lang.Thread还好,这样 UncaughtExceptionHandler 还能起作用。不过如果是线程池的话你就得小心了。如果你提交的是Runnable对象的话(就像上面那个一样,没有返回值),你得将整个方法体用try- catch包起来,至少打印一下异常。如果你提交的是Callable<Integer>的话,得确保你在用get()方法取值的时候重新抛 出异常:
final Future<Integer> division = executorService.submit(() -> 1 / 0);
//below will throw ExecutionException caused by ArithmeticException
division.get();

监控队列长度,确保队列有界

不当的线程池大小会使得处理速度变慢,稳定性下降,并且导致内存泄露。如果配置的线程过少,则队列会持续变大,消耗过多内存。而过多的线程又会 由于频繁的上下文切换导致整个系统的速度变缓——殊途而同归。队列的长度至关重要,它必须得是有界的,这样如果线程池不堪重负了它可以暂时拒绝掉新的请 求:
final BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(100);
executorService = new ThreadPoolExecutor(n, n,
0L, TimeUnit.MILLISECONDS,
queue);
上面的代码等价于Executors.newFixedThreadPool(n),然而不同的是默认的实现是一个无界的 LinkedBlockingQueue。这里我们用的是一个固定100大小的ArrayBlockingQueue。也就是说如果已经有100个任务在 队列中了(还有N个在执行中),新的任务就会被拒绝掉,并抛出RejectedExecutionException异常。由于这里的队列是在外部声明 的,我们还可以时不时地调用下它的size()方法来将队列大小记录在到日志/JMX/或者你所使用的监控系统中。

Executors.newCacheThreadPool线程和new ThreadPoolExecutor的区别和用法

Executors.去创建线程

五种线程池的适应场景

  • newCachedThreadPool:用来创建一个可以无限扩大的线程池,适用于服务器负载较轻,执行很多短期异步任务。
  • newFixedThreadPool:创建一个固定大小的线程池,因为采用无界的阻塞队列,所以实际线程数量永远不会变化,适用于可以预测线程数量的业务中,或者服务器负载较重,对当前线程数量进行限制。
  • newSingleThreadExecutor:创建一个单线程的线程池,适用于需要保证顺序执行各个任务,并且在任意时间点,不会有多个线程是活动的场景。
  • newScheduledThreadPool:可以延时启动,定时启动的线程池,适用于需要多个后台线程执行周期任务的场景。
  • newWorkStealingPool:创建一个拥有多个任务队列的线程池,可以减少连接数,创建当前可用cpu数量的线程来并行执行,适用于大耗时的操作,可以并行来执行

为什么Executors创建线程不安全

Java中的BlockingQueue主要有两种实现,分别是ArrayBlockingQueue 和 LinkedBlockingQueue。
ArrayBlockingQueue是一个用数组实现的有界阻塞队列,必须设置容量。
LinkedBlockingQueue是一个用链表实现的有界阻塞队列,容量可以选择进行设置,不设置的话,将是一个无边界的阻塞队列,最大长度为Integer.MAX_VALUE。
这里的问题就出在:不设置的话,将是一个无边界的阻塞队列,最大长度为Integer.MAX_VALUE。也就是说,如果我们不设置LinkedBlockingQueue的容量的话,其默认容量将会是Integer.MAX_VALUE。
而newFixedThreadPool中创建LinkedBlockingQueue时,并未指定容量。此时,LinkedBlockingQueue就是一个无边界队列,对于一个无边界队列来说,是可以不断的向队列中加入任务的,这种情况下就有可能因为任务过多而导致内存溢出问题。
上面提到的问题主要体现在newFixedThreadPool和newSingleThreadExecutor两个工厂方法上,并不是说newCachedThreadPool和newScheduledThreadPool这两个方法就安全了,这两种方式创建的最大线程数可能是Integer.MAX_VALUE,而创建这么多线程,必然就有可能导致OOM。

 

一、区别总结:

  1. Callable定义的方法是call,而Runnable定义的方法是run。
  2. Callable的call方法可以有返回值,而Runnable的run方法不能有返回值,这是核心区别。
  3. Callable的call方法可抛出异常,而Runnable的run方法不能抛出异常。

二、返回值的区别

  他们的核心区别是Callable可以返回Feature的对象,这个对象可以了解线程的运行情况,设置可以关闭线程!

三、Runnable代码事例

复制代码

package com.qunar.synchro;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Created by qiu.li on 2015/9/21.
 * 这是一个继承Runnable的例子
 */
public class TestRunnable implements Runnable {

    public static void main(String[] args) {

        ExecutorService runnableService = Executors.newFixedThreadPool(3);

        Runnable r1 = new TestRunnable();
        runnableService.submit(r1);
        runnableService.submit(new TestRunnable());
        runnableService.submit(new TestRunnable());
        runnableService.submit(new TestRunnable());
        runnableService.shutdown();
        
        System.out.println("go on");
        System.out.println("end");
    }

    @Override
    public void run() {
        for(int i=0;i<5; i++) {
            System.out.println(Thread.currentThread().getName() + ";random:" + (int) (Math.random() * 10 * 1000));
            try {
                Thread.sleep( (int) (Math.random() * 10 * 1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

复制代码

他的输出也比较简单,

复制代码

pool-1-thread-2;random:9491
go on
end
pool-1-thread-3;random:6983
pool-1-thread-1;random:718
pool-1-thread-2;random:4214.....

Process finished with exit code 0

复制代码

四、Callable代码

复制代码

package com.qunar.synchro;

import com.sun.org.apache.xalan.internal.utils.FeatureManager;

import java.util.concurrent.*;

/**
 * Created by qiu.li on 2015/9/21.
 */
public class TestCallable implements Callable<Boolean> {

    int i;

    public static void main(String[] args) {

        ExecutorService runnableService = Executors.newFixedThreadPool(3);

        Future<Boolean> r1 =  runnableService.submit(new TestCallable(1));
        Future<Boolean> r2 =  runnableService.submit(new TestCallable(2));
        Future<Boolean> r3 =  runnableService.submit(new TestCallable(3));
        try {
            boolean b2 = r2.get(); //r2先跑
            boolean b3 = r3.get(); //r3先跑
            System.out.println(b2);
            System.out.println(b3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        r1.cancel(true);//r1是死循环,现在退出
        runnableService.shutdownNow();
    }

    public TestCallable(int i){
        this.i = i;
    }

    @Override
    public Boolean call() {
        try {
            switch (i){
                case 1:
                    while(true) {
                        System.out.println(Thread.currentThread().getName() + ";i:" + this.i); //第一个线程
                        Thread.sleep(200);
                    }
                default:
                    Thread.sleep(500);
                    System.out.println(Thread.currentThread().getName() + ";i:" + this.i); //其他线程
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return true;
    }
}

复制代码

运行的结果:

复制代码

pool-1-thread-1;i:1
pool-1-thread-1;i:1
pool-1-thread-1;i:1
java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at com.qunar.synchro.TestCallable.call(TestCallable.java:46)
    at com.qunar.synchro.TestCallable.call(TestCallable.java:10)
    at java.util.concurrent.FutureTask.run(FutureTask.java:262)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    at java.lang.Thread.run(Thread.java:745)
pool-1-thread-2;i:2
pool-1-thread-3;i:3
true
true

Process finished with exit code 0

复制代码

大家可以看见抛出的异常,这是因为在线程1被阻塞的时候(比如被Object.wait, Thread.join和Thread.sleep三种方法之一阻塞时),由于没有占用CPU,是不能给自己的中断状态置位的,这就会产生一个InterruptedException异常。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值