Java并发编程之线程池的了解与使用

前言

在Java语言中,一切都可以看作是对象,如果要使用对象,那么就new一个出来,线程也是如此。要使用线程,那么肯定是先去创建一个线程,使用完毕后就将线程销毁,这个操作在我们现在的硬件条件下,执行速度是相当快的。但是如果并发线程数量很多的时候,那情况就不一样了,积少成多,会严重地减少相应的速度。那么能不能将使用过的线程先保存下来,在需要线程的时候直接去调用?针对这样的情况,Java中正好有线程池,通过线程池能够在一定程度上减少多个线程运行的时间。


一、ThreadPoolExecutor

并发编程中,JUC是一个关键的包,线程池所涉及的信息也在这个包中。在JUC中,有一个类为ThreadPoolExecutor,这个类是线程池的核心,了解这个类能够帮助我们更好地理解线程池。
ThreadPoolExecutor提供了四个构造方法:

 public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }
public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             threadFactory, defaultHandler);
    }
public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), handler);
    }
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.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

虽然是四个构造方法,事实上前三个构造方法都是调用的第四个方法进行初始化,而他们的不同就是在于形参上的不同。
线程池中的参数共有七种:corePoolSize 、maximumPoolSize 、keepAliveTime、
TimeUnit、workQueue、ThreadFactory、RejectedExecutionHandler。
在了解这些参数之前,我们不妨先看下线程池的运行流程:
在这里插入图片描述文字表述起来就是:

当线程池执行线程的时候,会先判断当前线程数有没有达到corePoolSize 。如果没有达到,那么就创建一个线程去执行任务,并将当前池中线程数量加1,如果数量超过了corePoolSize,那么去判断任务队列中有没有放满线程。如果没有放满线程,就将任务放到workQueue任务队列中进行等待,当corePoolSize中有空余位置了,workQueue中的任务就去执行。如果放满了线程,那么就去创建临时线程执行任务。但是临时线程的数量不一定是无限制的,它的数量就是maximumPoolSize -corePoolSize ,一旦临时线程的数量超过了这个值,线程池就会执行拒绝策略。

举个例子来看下线程池的流程:

package com.dong.ThreadPool;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @author 雪浪风尘
 * @Remember Keep thinking
 */
public class ThreadPoolProcess {
    public static void main(String[] args) {
        ThreadPoolExecutor poolExecutor=new ThreadPoolExecutor(5,10,40, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(5));
        for (int i=0;i<15;i++){
            MyTask2 task2=new MyTask2(i);
            poolExecutor.execute(task2);
            System.out.println("线程池中的数目:"+poolExecutor.getPoolSize()+";消息队列中的任务数目:"+poolExecutor.getQueue().size()+
                    ";已执行完的线程数"+poolExecutor.getCompletedTaskCount());
        }
        poolExecutor.shutdown();
    }
}
class MyTask2 implements Runnable{
    private int taskNum;
    public MyTask2(int num){
        this.taskNum=num;
    }
    @Override
    public void run() {
        System.out.println("正在执行第:"+taskNum+"个任务");
        try {
            Thread.sleep(1000);
        }catch (Exception e){
            e.printStackTrace();
        }
        System.out.println("第"+taskNum+"个任务已经执行完毕");
    }

}

运行结果:

正在执行第:0个任务
线程池中的数目:1;消息队列中的任务数目:0;已执行完的线程数0
线程池中的数目:2;消息队列中的任务数目:0;已执行完的线程数0
线程池中的数目:3;消息队列中的任务数目:0;已执行完的线程数0
正在执行第:1个任务
线程池中的数目:4;消息队列中的任务数目:0;已执行完的线程数0
线程池中的数目:5;消息队列中的任务数目:0;已执行完的线程数0
线程池中的数目:5;消息队列中的任务数目:1;已执行完的线程数0
线程池中的数目:5;消息队列中的任务数目:2;已执行完的线程数0
线程池中的数目:5;消息队列中的任务数目:3;已执行完的线程数0
线程池中的数目:5;消息队列中的任务数目:4;已执行完的线程数0
线程池中的数目:5;消息队列中的任务数目:5;已执行完的线程数0
线程池中的数目:6;消息队列中的任务数目:5;已执行完的线程数0
线程池中的数目:7;消息队列中的任务数目:5;已执行完的线程数0
正在执行第:2个任务
线程池中的数目:8;消息队列中的任务数目:5;已执行完的线程数0
线程池中的数目:9;消息队列中的任务数目:5;已执行完的线程数0
正在执行第:3个任务
线程池中的数目:10;消息队列中的任务数目:5;已执行完的线程数0
正在执行第:4个任务
正在执行第:10个任务
正在执行第:11个任务
正在执行第:12个任务
正在执行第:13个任务
正在执行第:14个任务
第0个任务已经执行完毕
正在执行第:5个任务
第1个任务已经执行完毕
正在执行第:6个任务
第2个任务已经执行完毕
正在执行第:7个任务
第3个任务已经执行完毕
第4个任务已经执行完毕
正在执行第:8个任务
正在执行第:9个任务
第12个任务已经执行完毕
第10个任务已经执行完毕
第13个任务已经执行完毕
第11个任务已经执行完毕
第14个任务已经执行完毕
第5个任务已经执行完毕
第6个任务已经执行完毕
第7个任务已经执行完毕
第9个任务已经执行完毕
第8个任务已经执行完毕

当任务进来的时候,核心线程先工作,然后再放到队列。

二、参数介绍

corePoolSize :核心池数量大小。从上面的流程图就可以看出来,他确实是属于核心的,每当有任务过来的时候,都要去判断它有没有能力去执行新的任务,只有它达到了设置的最大值,才会安排别人来处理新的任务。个人理解就是有活你先干,别人先在旁边看,比如这个样子:
在这里插入图片描述
在线程池中,默认情况下创建了线程池只是一个空的池子,当有任务的时候才会去创建线程去执行任务,不过可以通过prestartAllCoreThreads()方法创建corePoolSize个线程,或者通过prestartCoreThread()方法一个线程。

maximumPoolSize:线程池中共可以存放的线程数,也就是核心线程数+其它线程数。

keepAliveTime:线程存活时间。当线程执行任务完毕之后,不可能一直存在不销毁,不然的话线程一多,cpu就直接满了。所以通过设置keepAliveTime,可以让线程在执行完任务之后,还能存活一段时间,这段时间没有任务执行,它才会销毁。

TimeUnit:线程存活时间的单位。
TimeUnit提供了七种时间单位:

TimeUnit.DAYS; 天
TimeUnit.HOURS; 小时
TimeUnit.MINUTES; 分
TimeUnit.SECONDS; /秒
TimeUnit.MILLISECONDS; 毫秒
TimeUnit.MICROSECONDS; 微秒
TimeUnit.NANOSECONDS; 纳秒

workQueue:任务队列:用来存放等待中的任务。一般使用到三种。

ArrayBlockingQueue:基于数组的先进先出队列,创建队列的时候必须指定大小。
LinkedBlockingQueue:基于链表的先进先出队列,在创建的时候如果不指定大小,那么默认大小就是Integer.MAX_VALUE
SynchronousQueue:这个队列不会保存提交的任务,而是新建一个线程来执行新来的任务。

ThreadFactory:线程工厂,主要用来创建线程。

RejectedExecutionHandler:拒绝策略:当临时任务也不能创建的时候,就会触发拒绝策略。拒绝策略一般是以下四种:

ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

除此之外还有largestPoolSize:它是记录这个线程池在过程中的历史最大线程数。

三、运行状态

在学习线程的时候,我们就知道线程有五种状态:创建、就绪、运行、阻塞、销毁,同样的,存放线程的线程池也是有一定的运行状态的。
在JDK中定义了线程池的运行状态:

private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;

当线程池创建初始化的时候,处于RUNNING状态。
当线程池执行了shutdown方法后,线程池处于SHUTDOWN状态,此时线程池不会接收新的任务,但是会执行剩余未完成的任务。
当线程池执行了shotdownNow()方法后,线程池处于STOP状态,并且尝试去停止未完成的任务。
当线程处于SHUTDOWN或者STOP的时候,并且队列中的任务都被清空,所有任务都执行完毕了,就处于TERMINATED状态。

四、四种线程池

4.1、定长线程池

在之前的线程池流程代码实例中,我们使用了ThreadPoolExecutor创建了一个自定义线程池,但是在Java中有Executors来创建指定的线程池。
newFixedThreadPool:创建指定大小的线程池。表示线程池中最多有指定数量的线程。

package com.dong.ThreadPool;

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

/**
 * @author 雪浪风尘
 * @Remember Keep thinking
 */
class fixedThreadPool implements Runnable{
    private int num;
    public fixedThreadPool(int numTra){
        this.num=numTra;
    }
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"正在执行");
        try {
            Thread.sleep(1000);
        }catch (Exception e){
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"执行完毕");
    }
}
public class newFixedThreadPool {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        for (int i=1;i<=10;i++){
            fixedThreadPool threadPool=new fixedThreadPool(i);
            executorService.execute(threadPool);
        }
        executorService.shutdown();
    }
}

执行结果:

pool-1-thread-1正在执行
pool-1-thread-2正在执行
pool-1-thread-3正在执行
pool-1-thread-1执行完毕
pool-1-thread-1正在执行
pool-1-thread-2执行完毕
pool-1-thread-3执行完毕
pool-1-thread-2正在执行
pool-1-thread-3正在执行
pool-1-thread-1执行完毕
pool-1-thread-1正在执行
pool-1-thread-2执行完毕
pool-1-thread-3执行完毕
pool-1-thread-2正在执行
pool-1-thread-3正在执行
pool-1-thread-1执行完毕
pool-1-thread-1正在执行
pool-1-thread-3执行完毕
pool-1-thread-2执行完毕
pool-1-thread-1执行完毕

可以看到,指定线程池的size为3,运行的结果中线程只有1、2、3三个线程。

4.2、单线程线程池

newSingleThreadExecutor:线程池中只有线程。这种情况下会循环使用线程池中的此线程。

package com.dong.ThreadPool;

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

/**
 * @author 雪浪风尘
 * @Remember Keep thinking
 */
class singleThreadPool implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"开始执行");
        try {
            Thread.sleep(1000);
        }catch (Exception e){
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"执行完毕");
    }
}
public class newSingleThreadPool {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        for (int i=0;i<10;i++){
            singleThreadPool single=new singleThreadPool();
            executorService.execute(single);
        }
        executorService.shutdown();
    }
}

运行结果:

pool-1-thread-1开始执行
pool-1-thread-1执行完毕
pool-1-thread-1开始执行
pool-1-thread-1执行完毕
pool-1-thread-1开始执行
pool-1-thread-1执行完毕
pool-1-thread-1开始执行
pool-1-thread-1执行完毕
pool-1-thread-1开始执行
pool-1-thread-1执行完毕
pool-1-thread-1开始执行
pool-1-thread-1执行完毕
pool-1-thread-1开始执行
pool-1-thread-1执行完毕
pool-1-thread-1开始执行
pool-1-thread-1执行完毕
pool-1-thread-1开始执行
pool-1-thread-1执行完毕
pool-1-thread-1开始执行
pool-1-thread-1执行完毕

可以看到,运行结果中只有一个线程。

4.3、定时线程池

newScheduledThreadPool:此种线程池可以指定时间去执行线程任务。

package com.dong.ThreadPool;

import org.omg.CORBA.TIMEOUT;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * @author 雪浪风尘
 * @Remember Keep thinking
 */
class scheduleThreadPool implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"开始执行");
        System.out.println(Thread.currentThread().getName()+"执行完毕");
    }
}
public class newScheduleThreadPool {
    public static void main(String[] args) {
        ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(5);
        //延迟1秒执行
        scheduleThreadPool threadPool=new scheduleThreadPool();
        scheduledPool.schedule(threadPool,1, TimeUnit.SECONDS);
        //延迟1秒后每3秒执行一次

        /*scheduleThreadPool threadPool=new scheduleThreadPool();
        scheduledPool.scheduleAtFixedRate(threadPool,1,3,TimeUnit.SECONDS);*/
    }
}

4.4、可缓存线程池

newCachedThreadPool:创建能够缓存线程的线程池,使得后来的任务能够复用之前的线程。

package com.dong.ThreadPool;

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

/**
 * @author 雪浪风尘
 * @Remember Keep thinking
 */
class cacheThreadPool implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"开始执行");

        System.out.println(Thread.currentThread().getName()+"执行完毕");
    }
}
public class newCacheThreadPool {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i=0;i<10;i++){
            try {
                Thread.sleep(1000);
            }catch (Exception e){
                e.printStackTrace();
            }
            cacheThreadPool cachePool=new cacheThreadPool();
            executorService.execute(cachePool);
        }
        executorService.shutdown();
    }
}

执行结果:

pool-1-thread-1开始执行
pool-1-thread-1执行完毕
pool-1-thread-1开始执行
pool-1-thread-1执行完毕
pool-1-thread-1开始执行
pool-1-thread-1执行完毕
pool-1-thread-1开始执行
pool-1-thread-1执行完毕
pool-1-thread-1开始执行
pool-1-thread-1执行完毕
pool-1-thread-1开始执行
pool-1-thread-1执行完毕
pool-1-thread-1开始执行
pool-1-thread-1执行完毕
pool-1-thread-1开始执行
pool-1-thread-1执行完毕
pool-1-thread-1开始执行
pool-1-thread-1执行完毕
pool-1-thread-1开始执行
pool-1-thread-1执行完毕

注意

线程池在使用完之后,需要关闭线程池,否则线程池一直运行会极大增大cpu使用。

执行任务:execute()与submit()都可以执行任务,execute()无返回值,而submit()有返回值,但是submit()底层仍然是使用的execute()。

线程池中使用的类都是实现自Executor接口:

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);
}

这个最原始的接口只有一个方法execute,这个方法中只有一个形参Runnable,所以使用线程池的时候需要涉及到Runnbale。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值