Java--线程池

一、线程池
1、池化技术
池化技术减少了资源对象的创建次数,从而提高程序的性能,特别是在高并发的情况有明显的提高效率的作用。使用池化技术缓存的资源对象有以下特点:对象创建时间长、创建需要大量资源、创建后可被重复使用。
2、线程池
多线程可以最大限度发挥多核处理器的计算能力,但如果随意使用线程,对系统的性能反而不利。而现在,将多个线程放在一个“池子”里,需要线程的时候无需创建一个线程,而是从线程池里获取一个可用的线程,执行任务。线程池的关键在于管理了多个线程,不用关注线程如何创建,只关注核心业务。任务执行完后线程不会被销毁,而是被重新放到池子里继续等待着去执行任务。
3、线程池的优点
(1)提供一种简易的多线程编程方案,不用投入太多精力去管理多个线程,只在需要的时候去获取即可;
(2)减少线程创建与销毁的代价;
(3)有效控制线程池的最大并发数,避免大量的线程之间因抢占系统资源而阻塞;
(4)过多的线程易造成线程过度切换的结果,也容易抢占CPU资源,且大量线程的回收也会加大GC的压力,引入线程池能有效解决这类问题;
(5)能够对线程进行简单的管理,还可提供定时、定期、单线程、并发数控制等功能。
二、线程池的种类
1、newCachedThreadPool——可缓存线程池
(1)newCachedThreadPool是一种线程数量不定的线程池,并且其最大线程数为Integer.MAX_VALUE,这个数很大。如果线程池长度超过处理需要,该可缓存线程池可以灵活回收空闲线程,若无可回收则新建线程。但线程池中的空闲线程都有超时限制,超时时长是60秒,超过60秒闲置线程就会被回收。在以前构造的线程可用的情况下调用execute将重用以前构造的线程。
注:可缓存线程池适合执行大量、耗时较少的任务,当整个线程池都处于闲置状态时,线程池中的线程会因超时被停止。
(2)作用
创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们,并在需要时使用提供的ThreadFactory创建新线程。
(3)特征:
①线程池中数量没有固定,可达到最大值MAX_VALUE;
②线程池中的线程可进行缓存重复利用和回收;
③当线程池中,没有可用线程,会重新创建一个线程。
(4)创建方式:Executors.newCachedThreadPool();
创建示例

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

public class Test {
    public static void main(String[] args) {
        ExecutorService pool = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            final int index = i;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            pool.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("线程" + index + Thread.currentThread().getName());
                }
            });
        }
    }
}

运行结果显示所有线程名字均为pool-1-thread-1,即执行第二个任务的时第一个任务已经完成,复用执行第一个任务的线程,不用每次新建线程。
(5)适用场景:执行很多短期异步的小程序或者负载较轻的服务器。
2、newFixedThreadPool——定长线程池
(1)newFixedThreadPool创建一个指定工作线程数量的线程池,每提交一个任务就创建一个工作线程。当线程处于空闲状态时并不会被回收,除非线程池被关闭。如果工作线程数量达到线程池初始的最大值,会将提交的任务存入到池队列中(队列没有大小限制)。
注:newFixedThreadPool只有核心线程,且这些核心线程不会被回收,因此更快速相应外界的请求。适用于CPU资源稀缺,不随便创建新线程的场景。
(2)作用
①创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。在任意点,大多数线程会处于处理任务的活动状态。
②如果在所有线程处于活动状态时提交附加任务,则在有可用线程之前,附加任务将在队列中等待。如果在关闭前的执行期间由于失败而导致任何线程终止,那么一个新线程将代替它执行后续的任务(如果需要)。在某个线程被显式地关闭之前,池中的线程将一直存在。
(3)特征
①线程池中的线程处于一定的量,可以很好的控制线程的并发量;
②线程可以重复被使用,在显示关闭之前,都将一直存在;
③超出一定量的线程被提交时候需在队列中等待。
(4)创建方式
①Executors.newFixedThreadPool(int nThreads); //nThreads为线程的数量
②Executors.newFixedThreadPool(int nThreads,ThreadFactory threadFactory); //threadFactory创建线程的工厂方式
创建示例

public static void main(String[] args) throws InterruptedException {
            ExecutorService pool = Executors.newFixedThreadPool(5);//设置最大线程数5个
            for(int i = 0;i < 10;i++ ) {
                final int index = i;
                pool.execute(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("时间"+System.currentTimeMillis()+"线程" +index+Thread.currentThread().getName());
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                });
            }
        }
    }

在这里插入图片描述
设定的最大值为5,因此在创建了五个线程后等待1000ms继续完成创建。
(5)适用场景:执行长期的任务。
3、newScheduledThreadPool——定长线程池
(1)newScheduledThreadPool创建一个线程池,核心线程数量固定,而非核心线程数量没有限制,且当非核心线程闲置时会被立即回收,它可安排给定延迟后运行命令或者定期地执行。
注:这类线程池主要用于执行定时任务和具有固定周期的重复任务。
(2)作用
创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
(3)特征
①线程池中具有指定数量的线程,即便是空线程也将保留;
②可定时或者延迟执行线程活动。
(4)创建方式
①Executors.newScheduledThreadPool(int corePoolSize);
②newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory);
定时任务创建示例

public static void main(String[] args) throws InterruptedException {
        //设置核心数量2
        ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);
        System.out.println("时间:" + System.currentTimeMillis());
        pool.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("时间:" + System.currentTimeMillis());
            }
        }, 1, TimeUnit.SECONDS);//设置延迟1秒执行
    }

定期任务创建示例

public static void main(String[] args) throws InterruptedException {
        //设置核心数量2
        ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);
        System.out.println("时间:" + System.currentTimeMillis());
        pool.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println("时间:" + System.currentTimeMillis());
            }
        }, 1, 3, TimeUnit.SECONDS);//设置延迟1秒后每3秒执行一次
    }

(5)适用场景:周期性执行任务的场景。
4、newSingleThreadExecutor——单线程化线程池
(1)newSingleThreadExecutor内部只有一个核心线程,以无界队列方式来执行该线程,这使得这些任务之间不需要处理线程同步的问题,确保所有的任务都在同一个线程中按顺序执行。
(2)作用
①创建一个使用单个worker线程的 Executor,以无界队列方式来运行该线程。
注:如果因为在关闭前的执行期间出现失败而终止了此单个线程,那么如果需要,会有一个新线程代替它执行后续的任务。
②保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。与其等效的newFixedThreadPool(1) 不同,newSingleThreadExecutor可保证无需重新配置此方法所返回的执行程序即可使用其他的线程。
(3)特征
线程池中最多执行1个线程,之后提交的线程活动将会排在队列中以此执行。
(4)创建方式
①Executors.newSingleThreadExecutor();
②Executors.newSingleThreadExecutor(ThreadFactory threadFactory);
创建示例

public static void main(String[] args) throws InterruptedException {
        ExecutorService pool = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 10; i++) {
            final int number = i;
            pool.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("时间:" + System.currentTimeMillis() + "线程" + number);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }

(5)适用场景:一个任务一个任务执行的场景。
5、newSingleThreadScheduledExecutor——单线程执行程序
(1)作用
创建一个单线程执行程序,它可安排在给定延迟后运行命令或者定期地执行。
(2)特征
①线程池中最多执行1个线程,之后提交的线程活动将会排在队列中以此执行
②可定时或者延迟执行线程活动
(3)创建方式
①Executors.newSingleThreadScheduledExecutor();
②Executors.newSingleThreadScheduledExecutor(ThreadFactory threadFactory);
6、一些基本操作

//创建一个线程池
ExecutorService executorService = Executors.newFixedThreadPool(1);

//提交任务
executorService.submit(() -> System.out.println("run"));
Future<String> stringFuture = executorService.submit(() -> "run");

//创建一个调度线程池
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);

//提交一个周期性执行的任务
scheduledExecutorService.scheduleAtFixedRate(() -> System.out.println("schedule"),0,1, TimeUnit.SECONDS);

//关闭
executorService.shutdown();
scheduledExecutorService.shutdown();

注:shutdown与shutdownNow的区别
shutdown只是将线程池的状态设置为SHUTDOWN状态,正在执行的任务会继续执行下去,没有被执行的则中断;
shutdownNow则是将线程池的状态设置为STOP,正在执行的任务被停止,没被执行的返回。
举例:一个笼子(线程池)里的兔子(线程)吃萝卜(任务),接收到shutdown命令后,正在吃萝卜的兔子先把手头上的给吃完,没拿到的萝卜不能吃;而接受到shutdownNow的话,所有的都立刻停止吃萝卜,手头上没吃完的也得放下。
三、Executor继承关系
在这里插入图片描述
1、继承关系
Java中与线程池相关的类在java.util.concurrent包下,常用有Executor、Executors、ExecutorService、ScheduledExecutorService、ScheduledThreadPoolExecutor、ThreadPoolExecutor。
(1)Executor是线程池的鼻祖类,有两个子类ExecutorService和ScheduledExecutorService。
(2)Executors类是一个工厂类,可以产生不同类型的线程池,其角色就是创建线程池。
(3)ThreadPoolExecutor和ScheduledThreadPoolExecutor是真正的线程池,任务将被这两个类交由其所管理者的线程池运行。
(4)ScheduledThreadPoolExecutor继承了ThreadPoolExecutor,增加了调度功能;ThreadPoolExecutor实现了一般的线程池,没有调度功能。
(5)Executor是一个顶层接口,里面只声明了一个方法execute(Runnable command),返回值为void,参数为Runnable类型,用来执行传进去的任务。
(6)ExecutorService接口继承了Executor接口,并声明了一些方法如submit、invokeAll、invokeAny以及shutDown等。
(7)抽象类AbstractExecutorService实现了ExecutorService接口,基本实现了ExecutorService中声明的所有方法。
(8)ThreadPoolExecutor继承了类AbstractExecutorService。
2、常用方法
(1)execute()
实际上是Executor中声明的方法,在ThreadPoolExecutor进行了具体的实现,这个方法是ThreadPoolExecutor的核心方法,通过这个方法可以向线程池提交一个任务,交由线程池去执行。
(2)submit()
是在ExecutorService中声明的方法,在AbstractExecutorService就已经有了具体的实现,在ThreadPoolExecutor中并没有对其进行重写,这个方法也是用来向线程池提交任务的,但是它和execute()方法不同,它能够返回任务执行的结果,去看submit()方法的实现,会发现它实际上还是调用的execute()方法,只不过它利用了Future来获取任务执行结果。
(3)void shutdown()
有序关闭已经提交的任务,但是不再接受新的任务,重复shutdown无效.而且此方法不会等待已提交任务的执行完毕.
(4)List shutdownNow()
尝试停止所有正在执行的任务,终止所有处于等待队列中的任务,并将这些等待被执行的任务返回给调用者
(5)boolean isShutdown()
判断线程池是否已关闭
(6)boolean isTerminated()
当调用了showdown()方法后,所有任务是否已执行结束.注意:如果不事先调用showdown()方法,则此方法永远返回false.
(7)boolean awaitTermination(long timeout, TimeUnit unit)
当调用shutdown()方法后,调用此方法可以设置等待时间,等待执行中的任务全部结束,全部结束返回true.如果超时,或线程中断导致未全部结束则返回false.
(8) Future submit(Callable task)
提交有返回值的Runnable任务.
(9) List<Future> invokeAll(Collection<? extends Callable> tasks,long timeout, TimeUnit unit)
执行传入的任务,当所有任务执行结束或超时后,返回持有任务状态和结果的Future集合.注意:一个任务结束有两种情况:1.正常执行完成;2.抛出异常.
(10) T invokeAny(Collection<? extends Callable> tasks)
执行传入的任务,只要有任何一个任务成功执行完成(不抛出异常),一旦有结果返回或抛出异常,则其他任务取消。
四、ThreadPoolExecutor
1、继承关系

public class ThreadPoolExecutor extends AbstractExecutorService

2、属性分析

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }
//核心线程数
//核心线程是指线程池一启动的时候分配的线程
//任务数量小于核心线程数量,会使用核心线程去执行,并且执行完是不回收的
//任务数量大于核心线程数量,就会进入阻塞队列,入队之后队列里的任务就由非核心线程来执行;非核心线程如果在空闲后的keepAliveTime内还没活就会被回收
int corePoolSize;

//表示允许进入线程池的最大线程数
int maximumPoolSize;

//保持激活时间
//当线程数大于核心数时,这是多余的空闲线程在终止之前等待新任务的最大时间                              
long keepAliveTime;

//keepAliveTime的时间单位
TimeUnit unit;

//线程池的缓冲队列
//即执行前用于保持任务的队列。此队列仅保持由execute方法提交的Runnable任务
BlockingQueue<Runnable> workQueue;

//初始化创建新线程
ThreadFactory threadFactory;

//默认被拒绝的执行策略
//线程池被占满时,不再开启新的任务
RejectedExecutionHandler handler;

在这里插入图片描述
归纳
(1)如果当前线程池中的线程数目<corePoolSize,则每来一个任务,就会创建一个线程去执行这个任务;
(2)如果当前线程池中的线程数目>=corePoolSize,则每来一个任务,会尝试将其添加到任务缓存队列当中,若添加成功,则该任务会等待空闲线程将其取出去执行;若添加失败(一般来说是任务缓存队列已满),则会尝试创建新的线程去执行这个任务;
(3)如果当前线程池中的线程数目达到maximumPoolSize,则会采取任务拒绝策略进行处理;
(4)如果线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止,直至线程池中的线程数目不大于corePoolSize;如果允许为核心池中的线程设置存活时间,那么核心池中的线程空闲时间超过keepAliveTime,线程也会被终止。
注:任务拒绝策略
线程池有一个任务队列,用于缓存所有待处理的任务,正在处理的任务将从任务队列中移除,因此在队列长度有限的情况下就会出现新任务的拒绝处理问题,需要有一种策略来处理应该加入任务队列却因为队列已满无法加入的情况。
拒绝任务有两种情况:线程池已经被关闭、任务队列已满且maximumPoolSizes已满。
RejectedExecutionHandler提供了四种方式来处理任务拒绝策略:DiscardPolicy直接丢弃、DiscardOldestPolicy丢弃队列中最老的任务、AbortPolicy抛异常、CallerRunsPolicy将任务分给调用线程来执行。
3、线程池的状态
在ThreadPoolExecutor类中定义了一个volatile变量runState来表示线程池的状态,线程池有四种状态:
(1)RUNNING
线程池创建后处于RUNNING状态。
(2)SHUTDOWN
调用shutdown后处于SHUTDOWN状态,线程池不能接受新的任务,会等待缓冲队列的任务完成。
(3)STOP
调用shutdownNow后处于STOP状态,线程池不能接受新的任务,并尝试终止正在执行的任务。
(4)TERMINATED
当线程池处于SHUTDOWN或STOP状态,并且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为TERMINATED状态。
4、关于可能抛出的异常
(1)IllegalArgumentException
情况:①corePoolSize或keepAliveTime<0;②maximumPoolSize<=0;③corePoolSize>maximumPoolSize
(2)NullPointerException
情况:①workQueue为null;②threadFactory为null;③handler为null
五、关于线程池中Runnable和Callable
1、线程池在提交任务时候采用submit和execute两个方法,其中submit在ExecutorService中出现,被AbstractExecutorService继承重写。
2、从AbstractExecutorService的submit方法源码可以看出,两种类型的任务最终都会被转换成RunnableFuture:
(1)传入Runnable任务类型的参数

public Future<?> submit(Runnable task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<Void> ftask = newTaskFor(task, null);
        execute(ftask);
        return ftask;
    }

(2)传入Callable任务类型的参数

    public <T> Future<T> submit(Callable<T> task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task);
        execute(ftask);
        return ftask;
    }

RunnableFuture类,继承了Runnable和Future接口,只有一个run方法:

public interface RunnableFuture<V> extends Runnable, Future<V> {
    void run();
}

3、Runnable这个接口包含了一个抽象的run方法:

public interface Runnable {
    public abstract void run();
}

Callable接口包含了一个含有返回值且会抛出异常的call方法:

public interface Callable<V> {
    V call() throws Exception;
}

4、总结
(1)都可以被ExecutorService执行。
(2)Callable的call方法只能通过ExecutorService的submit(Callable task)执行,且会返回一个表示任务等待完成的Future,Callable的call方法,有返回值V,并且可能抛出异常;Runnable的run方法无返回值,无法抛出经过检查的异常。
(3)将Callable的对象传递给ExecutorService的submit方法,其call方法自动在一个线程上执行,并且会返回执行结果Future对象;将Runnable的对象传递给ExecutorService的submit方法,其run方法自动在一个线程上执行,并且会返回执行结果Future对象,但是在该Future对象上调用的方法返回的是null。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值