java线程池

本文详细介绍了Java线程池的基本概念、使用方法及其内部实现原理。涵盖了常用的线程池创建方式,如newCachedThreadPool、newSingleThreadExecutor等,并探讨了自定义线程池的实现细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

线程池

线程池的好处:

重用存在的线程,减少对象创建、消亡的开销。
有效控制最大并发数,提高系统资源使用率。
定时执行、定期执行。

线程池所在的包为java.util.concurrent
顶级接口为Executor,真正的线程池接口是ExecutorService
Executors类提供农创建线程池的方法

newCachedThreadPool() 创建一个可缓存的线程池,有任务是时才创建新线程
newSingleThreadExecutor() 创建一个单线程池
newFixedThreadPool() 创建有个固定长度的线程池,空闲线程会一直保留
newSecheduledThreadPool() 创建一个固定长度的的线程池,以延迟或固定的时间执行任务

使用newCachedThreadPool()创建线程池

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/*
 * 使用newCachedThreadPool()创建线程池
 */
public class ThreadCurrent {
    public static void main(String[] args) {
        ExecutorService pool = Executors.newCachedThreadPool();
        //让线程池去执行10个任务
        /*
         * 10个任务被线程池中的一个任务执行
         * 大大减少了线程的销和创建时间。更节省效率。
         * 当休眠省了以后,会有更多线程进来执行。
         * 当线程池中的线程执行完当前任务之后不是销毁而是去执行新的任务。增加了灵活性。
         */

        for (int i = 0; i < 10; i++) {
            pool.execute(new MyRunning(i));
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}
class MyRunning implements  Runnable{
    int num;

    public MyRunning(int num) {
        super();
        this.num = num;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"  "+num);
    }

}

这里写图片描述

让线程池中的一个线程去执行10次任务,减少了线程创建和销毁的时间。提高了程序效率。


使用newSingleThreadExecutor()创建线程

public class ThreadPool {
    public static void main(String[] args) {
        ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
        /*
         * 单线程池。线程池中只有一个线程执行
         * 当一个线程正在执行时,其他线程会等待当前线程执行完毕再执行
         */
        for (int i = 0; i < 10; i++) {
            singleThreadExecutor.execute(new MyRunning(i));
        }
    }
}
class MyRunning implements  Runnable{
    int num;

    public MyRunning(int num) {
        super();
        this.num = num;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"  "+num);
    }
}

这里写图片描述

单线程池,线程池中只存在一个线程。让这1个线程执行任务。与CachedPool相似。


使用fixedThreadPool创建线程

/*
 * 使用FixedThreadPool创建线程池
 */
public class ThreadPool {
    public static void main(String[] args) {
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
        /*
         * 最多只能是指定线程范围内的线程数
         * 
         */
        for (int i = 0; i < 10; i++) {
            fixedThreadPool.execute(new MyRunning(i));
        }
    }
}
class MyRunning implements  Runnable{
    int num;

    public MyRunning(int num) {
        super();
        this.num = num;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"  "+num);
    }
}

这里写图片描述

线程池中的线程随机来执行给定的任务。线程中的最大线程数量自己指定。


使用ScheduledThredPool创建线程池

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/*
 * 使用ScheduledThreadPool创建线程池
 */
public class ThreadPool {
    public static void main(String[] args) {
        /*
         * ScheduledThreadPool创建线程池
         * 具有延迟执行和周期执行的功能
         * ScheduledThreadPool的scheduleAtFixedRate()方法:
         *      该方法有四个参数:1.要执行的任务执行Running接口的线程类
         *                   2.首次执行的延迟时间
         *                   3.连续执行之间的周期
         *                   4.2、3参数的时间单位,枚举类型(DAYS/HOURS/SECONDS/MICROSECONDS/MINUTES)
         */
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
        System.out.println("************线程开始执行*******");
        //每个任务执行前有1秒钟的延迟。每2秒执行一次该任务
        scheduledThreadPool.scheduleAtFixedRate(new MyRunning(),5, 2, TimeUnit.SECONDS);
    }
}
class MyRunning implements  Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+" 延时5秒钟执行、每两秒钟执行一次 ");
    }
}

这里写图片描述

ScheduledThredPool具有延迟执行和线程周期执行的功能。


自定义线程池

当Excetors接口创建的线程池不足以满足需求时我们可以自己创建线程池类

利用ThreadPoolExecutor类

public class MyThreadPool {
    public static void main(String[] args) {
        //创建一个自定义线程池
        ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 7, 300,
                TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(4));
        for(int i=1;i<=12;i++){
            executor.execute(new MyRunning(i));
            System.out.println("线程池中的线程数:"+executor.getPoolSize()
                                +"。等待执行的任务数:"+executor.getQueue().size()
                                +"。已经执行完的任务数:"+executor.getCompletedTaskCount());
        }
        //关闭线程池
        executor.shutdown();
    }
}

class MyRunning implements Runnable {
    int num; // 第几个任务

    public MyRunning(int num) {
        super();
        this.num = num;
    }

    @Override
    public void run() {
        System.out.println("正在执行" + num);
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(num + "执行完毕");
    }

}


原理:

假设核心线程有5个、最大线程数有7个、最大队列数有4个、任务有12个
当核心线程装满5个后,会在队列装任务4个,队列中的线程处于等待状态。
当队列也满的时候,会遵循最大线程数来继续创建2个线程。
这时还有最后一个任务,但是这时没有多余的线程或者队列来执行任务, 那么就会遵循拒绝任务的策略来拒绝此任务,此任务被销毁。抛出异常。
当线程池中的全部任务都执行完毕时,依次从队列中接受任务新建线程继续执行任务。 当所有队列所有线程池中的任务都执行完毕后,会保留核心线程数5个, 其余线程会销毁。核心线程即使没有任务也会被保留下来。


来重点看自定义线程

java.uitl.concurrent.ThreadPoolExecutor类是线程池中最核心的一个类,因此如果要透彻地了解Java中的线程池,必须先了解这个类。

在ThreadPoolExecutor类中提供了四个构造方法:

public class ThreadPoolExecutor extends AbstractExecutorService {
    .....
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
            BlockingQueue<Runnable> workQueue);

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

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

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

从上面的代码可以得知,ThreadPoolExecutor继承了AbstractExecutorService类,并提供了四个构造器,事实上,通过观察每个构造器的源码具体实现,发现前面三个构造器都是调用的第四个构造器进行的初始化工作。

看看构造器中的参数

ThreadPoolExecutor构造中有跟多参数来规定自己的线程池所具有的功能
corePoolSize:核心池的大小。开始执行时的线城池的大
maximumPoolSize:线程池的最大线程数
keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止
unit:参数keepAliveTime的时间单位
workQueue:一个阻塞队列,用来存储等待执行的任务
threadFactory:线程工厂,主要用来创建线程
handler:表示当拒绝处理任务时的策略

unit:参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性。

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

workQueue:这里的阻塞队列有以下几种选择:

ArrayBlockingQueue;
LinkedBlockingQueue;
SynchronousQueue;

handler:表示当拒绝处理任务时的策略,有以下四种取值:

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

在ThreadPoolExecutor类中有几个非常重要的方法:

execute()   向线程池提交一个任务,交由线程池去执行。
submit()execute()相同,能返回任务执行的结果。
shutdown()      不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止按照提交顺序有序的关闭,不接受新任务。
shutdownNow()   立即停止所有的活动,包括暂停和等待活动。并返回等待任务列表
getOueue()      获取等待的任务队列
getPoolSize()   获取当前线程池中的线程数
getActiveCount()    获取执行任务的线程数
getCompletedTaskCount() 获取已完成的任务数

深入剖析线程池的实现原理

1.线程池的状态:
在ThreadPoolExecutor中定义了一个volatile变量,另外定义了几个static final变量表示线程池的各个状态:

volatile int runState;     //表示当前线程池的状态
static final int RUNNING    = 0;
static final int SHUTDOWN   = 1;
static final int STOP       = 2;
static final int TERMINATED = 3;

runState表示当前线程池的状态,它是一个volatile变量用来保证线程之间的可见性;
下面的几个static final变量表示runState可能的几个取值。
当创建线程池后,初始时,线程池处于RUNNING状态;

当创建线程池后,初始时,线程池处于RUNNING状态;
如果调用了shutdown()方法,则线程池处于SHUTDOWN状态,此时线程池不能够接受新的任务,它会等待所有任务执行完毕;
如果调用了shutdownNow()方法,则线程池处于STOP状态,此时线程池不能接受新的任务,并且会去尝试终止正在执行的任务;
当线程池处于SHUTDOWN或STOP状态,并且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为TERMINATED状态。

2.任务的执行
在了解将任务提交给线程池到任务执行完毕整个过程之前,我们先来看一下ThreadPoolExecutor类中其他的一些比较重要成员变量:

private final BlockingQueue<Runnable> workQueue;              //任务缓存队列,用来存放等待执行的任务
private final ReentrantLock mainLock = new ReentrantLock();   //线程池的主要状态锁,对线程池状态(比如线程池大小runState等)的改变都要使用这个锁
private final HashSet<Worker> workers = new HashSet<Worker>();  //用来存放工作集
private volatile long  keepAliveTime;    //线程存货时间   
private volatile boolean allowCoreThreadTimeOut;   //是否允许为核心线程设置存活时间
private volatile int   corePoolSize;     //核心池的大小(即线程池中的线程数目大于这个参数时,提交的任务会被放进任务缓存队列)
private volatile int   maximumPoolSize;   //线程池最大能容忍的线程数
private volatile int   poolSize;       //线程池中当前的线程数
private volatile RejectedExecutionHandler handler; //任务拒绝策略
private volatile ThreadFactory threadFactory;   //线程工厂,用来创建线程
private int largestPoolSize;   //用来记录线程池中曾经出现过的最大线程数
private long completedTaskCount;   //用来记录已经执行完毕的任务个数

3.任务缓存队列及排队策略

ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小;
LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE;
synchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。

4.任务拒绝策略
当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略:

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

5.线程池的关闭

shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务。
shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务。

6.线程池容量的动态调整

ThreadPoolExecutor提供了动态调整线程池容量大小的方法:

setCorePoolSize()和setMaximumPoolSize(),
setCorePoolSize:设置核心池大小
setMaximumPoolSize:设置线程池最大能创建的线程数目大小

当上述参数从小变大时,ThreadPoolExecutor进行线程赋值,还可能立即创建新的线程来执行任务。

并发线程编程:
参考:
http://ifeve.com/java-threadpool/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值