Java线程池

3. 线程池

3.1 概述

提前创建好若干个线程放在一个容器中。如果有任务需要处理,则直接将任务分配给线程池中的线程来处理,任务处理完以后当前线程不会被销毁,而是又被线程池回收,等待后续分配任务。减少线程创建、销毁的开销。

线程使用上的问题

  • 线程的频繁创建和销毁new Thread().start()会消耗CPU内存
  • 线程的数量过多,会造成CPU资源的开销,上下文切换过于频繁也会消耗CPU资源

所以使用线程池实现线程的复用

池化技术

连接池、对象池、线程池、内存池。。。

池化技术的核心:复用

线程池的好处

  • 降低创建、销毁线程带来的系统开销。
  • 提高响应速度,有新任务需要执行时不需要等待线程创建,可以立马执行。
  • 需要注意合理设置线程池大小,避免线程数过多,超过硬件资源瓶颈

线程池设计猜想

如何让线程实现复用 --> 让线程不结束,一直运行

线程一直运行,如何执行新的任务 --> 通过共享内存,使用生产者-消费者模型,生产者往共享内存中放数据,消费者从共享内存中取数据进行消费,从而实现新任务的执行

线程一直运行合理吗? --> 有任务就执行,没有任务就阻塞等待,所以用到阻塞队列,通过阻塞队列的take()方法获取任务

如果任务累计,会动态扩容消费者线程,如果扩容后依然无法解决问题,采用拒绝策略,默认报错

如何销毁扩容线程? --> 如果当前任务队列为空,那么扩容的消费线程闲置,需要进行销毁,如何判断线程需要销毁?从队列中取任务,在一定时间内如果没有取到任务,代表当前队列空闲,没有任务堆积,那么就需要对扩容的线程进行销毁

3.2 常用线程池

Java提供了四种线程池

  • Executors.newFixedThreadPool(5); //创建固定数量线程的线程池
  • Executors.newSingleThreadExecutor(); //创建只有一个线程的线程池
  • Executors.newCachedThreadPool(); //创建一个线程数可动态伸缩的线程池,有多少请求,就创建多少个线程去处理
  • Executors.newScheduledThreadPool(5); //创建带任务调度的线程池
public class ThreadPoolDemo implements Runnable {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        //ExecutorService executorService = Executors.newSingleThreadExecutor();
        //ExecutorService executorService = Executors.newCachedThreadPool();
        //ExecutorService executorService = Executors.newScheduledThreadPool(2);
        for (int i = 0; i < 10; i ++) {
            executorService.execute(new ThreadPoolDemo());
        }
        executorService.shutdown();
    }

    @Override
    public void run() {
        try {
            System.out.println("当前运行的线程是" + Thread.currentThread().getName());
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

3.3 线程池原理

3.3.1 线程池的基本原理

// 四种创建线程池的方法底层都是通过此方法创建
public ThreadPoolExecutor(int corePoolSize, //核心线程数
    int maximumPoolSize, //最大线程数
    long keepAliveTime, //超出核心线程数以外的线程空余存活时间
    TimeUnit unit, //存活时间单位
    BlockingQueue<Runnable> workQueue, //保存待执行任务的队列
    ThreadFactory threadFactory, //创建新线程的线程工厂
    RejectedExecutionHandler handler //当任务无法执行时的处理方式
) {}

线程池参数

  • 线程数量(核心线程数量)
  • 最大线程数量
  • 阻塞队列(保存任务的队列)
  • 拒绝策略
  • 针对扩容的线程,设置存活时间,存活时间单位
  • 创建线程的工厂:创建守护线程

守护线程(Daemon Thread):守护线程是用来服务用户线程的,比如GC就是守护线程,可以看做守护线程是用户线程的保姆;如果没有其他用户线程在运行,也就没有可服务对象,守护线程可以结束,守护线程没有执行完成,不影响JVM进程的结束。

非守护线程:有用户线程、事件调度线程,非守护线程没有执行完,会阻止JVM进程的结束。

3.3.2 线程池执行原理

线程池中核心线程是延迟初始化的,当有任务进来时才去初始化。

1) 先初始化核心线程
2) 调用阻塞队列方法,将task存入队列中。(offer() -> true / false)
3) 如果存放成功,代表当前请求量不大,核心线程可以处理
4) 如果存放失败,代表请求量很大,需要增加工作线程(非核心线程)
5) 如果添加线程失败,说明已经达到最大扩容线程数,采用拒绝策略

public void execute(Runnable command) {
    if (command == null) //如果线程为null,则报错
        throw new NullPointerException();
    int c = ctl.get(); //通过AtomicInteger获取正在工作的线程数
    // ctl是一个32位长度二进制数值,高三位代表线程状态,后29位代表线程数量
    if (workerCountOf(c) < corePoolSize) { //如果正在工作线程数 < 核心线程数
        if (addWorker(command, true)) //将创建一个new worker去执行任务
            return;
        c = ctl.get();
    }
    // workQueue.offer添加到阻塞队列
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        if (! isRunning(recheck) && remove(command))
            reject(command);
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    // addWorker如果阻塞队列满了,添加工作线程(扩容)
    else if (!addWorker(command, false))
        reject(command); //如果添加失败,采用拒绝策略
}

使用有界阻塞队列去存放请求执行的任务

如果请求数量非常多,队列满了,在线程池的场景中,不能去阻塞生产者线程,那么如何处理呢?

  • 增加消费者线程的数量(扩容)
  • 扩容是有限制的,如果扩容解决不了问题,采用以下拒绝策略
    • 报错
    • 丢弃这个任务
    • 普通线程调用task.run()
    • 队列头部等待最久的任务丢弃,然后把当前任务添加到阻塞队列
    • 存储起来,等待空闲后重试(需要自定义扩展完成)

3.3.3 线程池工作流程

1) 首先创建一个空的线程池(延迟初始化)。

2) 然后线程池提交任务以后,线程工厂去创建工作线程执行任务,这个时候创建的线程是核心线程。

3) 随着请求增加,有大量任务提交到线程池,此时工作线程数量达到了设置的核心线程数量,不再增加新的线程,那么新提交的任务就会存放在阻塞队列里。

4) 提交的任务越来越多,如果阻塞队列满了,此时会创建临时线程(非核心线程)去执行任务。

5) 如果继续不断的提交任务到阻塞队列,而创建的核心线程数量与临时线程数量总和达到了设置的最大线程数量,此时阻塞队列满了,无法往阻塞队列添加任务,也无法创建新的线程,那么会执行拒绝策略。拒绝策略有下面几种:

        a. 直接报错;
        b. 丢弃任务并抛出异常;
        c. 丢弃队列头部的任务,将新任务存入队列尾部;
        d. 由主线程来执行任务;
        e. 自己实现,比如将新任务缓存起来,等到线程空闲时候执行;

6) 随着线程不断执行,当任务数量不够所有线程进行消费了,那么部分线程就无法从队列中获取到任务去执行;如果设置了过期时间时间单位,那么到了过期时间仍然没有拿到任务,就会闲置的线程就会结束运行,自动销毁,只留下达到核心线程数量的线程。

3.4 源码分析

execute(Runnable command)

public void execute(Runnable command) {
	if (command == null)
		throw new NullPointerException();
	int c = ctl.get();
    // 判断当前工作线程数是否小于核心线程数(延迟初始化)
	if (workerCountOf(c) < corePoolSize) {
		if (addWorker(command, true))
			return;
		c = ctl.get();
	}
    // workQueue.offer() 添加任务到阻塞队列
	if (isRunning(c) && workQueue.offer(command)) {
		int recheck = ctl.get();
        // 再次检查线程的状态
		if (! isRunning(recheck) && remove(command))
			reject(command);
		else if (workerCountOf(recheck) == 0)
			addWorker(null, false);
	}
    // 如果上面添加任务到阻塞队列失败,可能阻塞队列满了,则添加工作线程去扩容,
    // 如果添加工作线程失败,可能是已达最大线程数,采取拒绝策略
	else if (!addWorker(command, false))
		reject(command); // 拒绝策略
}

addWorker(Runnable firstTask, boolean core)

private boolean addWorker(Runnable firstTask, boolean core) {
	retry:
    // case1: CAS原子操作增加线程数量
	for (;;) { //自旋
		int c = ctl.get();
		int rs = runStateOf(c);

		// Check if queue empty only if necessary.
		if (rs >= SHUTDOWN &&
				! (rs == SHUTDOWN &&
						firstTask == null &&
						! workQueue.isEmpty()))
			return false;

		for (;;) {
			int wc = workerCountOf(c);
            //判断工作线程是否满了,满了则创建失败
			if (wc >= CAPACITY ||
					wc >= (core ? corePoolSize : maximumPoolSize))
				return false;
			if (compareAndIncrementWorkerCount(c)) //CAS原子操作增加线程数量
				break retry;
			c = ctl.get();  // Re-read ctl
			if (runStateOf(c) != rs)
				continue retry;
			// else CAS failed due to workerCount change; retry inner loop
		}
	}

    // case2: 初始化工作线程
	boolean workerStarted = false;
	boolean workerAdded = false;
	ThreadPoolExecutor.Worker w = null;
	try {
        // 构建一个工作线程,但是此时还没有启动
		w = new ThreadPoolExecutor.Worker(firstTask);
		final Thread t = w.thread;
		if (t != null) {
			final ReentrantLock mainLock = this.mainLock;
			mainLock.lock(); //加锁保证下面操作的安全性
			try {
				// Recheck while holding lock.
				// Back out on ThreadFactory failure or if
				// shut down before lock acquired.
				int rs = runStateOf(ctl.get());

				if (rs < SHUTDOWN ||
						(rs == SHUTDOWN && firstTask == null)) {
					if (t.isAlive()) // precheck that t is startable
						throw new IllegalThreadStateException();
					workers.add(w); //将创建的线程添加到线程池中
					int s = workers.size();
					if (s > largestPoolSize) //更新线程池容量
						largestPoolSize = s;
					workerAdded = true; //添加成功
				}
			} finally {
				mainLock.unlock();
			}
			if (workerAdded) {
				t.start(); //启动线程
				workerStarted = true; //启动成功
			}
		}
	} finally {
		if (! workerStarted) //如果启动失败,回滚
			addWorkerFailed(w);
	}
	return workerStarted;
}

runWorker(Worker w)

重复的从队列获取任务并执行

final void runWorker(ThreadPoolExecutor.Worker w) {
	Thread wt = Thread.currentThread();
	Runnable task = w.firstTask;
	w.firstTask = null;
	w.unlock(); // allow interrupts
	boolean completedAbruptly = true;
	try {
        // while循环保证线程不结束,直到task为空,退出while循环,线程执行结束并销毁
		while (task != null || (task = getTask()) != null) {
			w.lock(); //worker继承AQS -> 实现互斥锁,避免其他地方结束此线程,保证安全性
			// If pool is stopping, ensure thread is interrupted;
			// if not, ensure thread is not interrupted.  This
			// requires a recheck in second case to deal with
			// shutdownNow race while clearing interrupt
			if ((runStateAtLeast(ctl.get(), STOP) ||
					(Thread.interrupted() &&
							runStateAtLeast(ctl.get(), STOP))) &&
					!wt.isInterrupted())
				wt.interrupt();
			try {
				beforeExecute(wt, task); //对外扩展方法
				Throwable thrown = null;
				try {
					task.run(); //执行任务
				} catch (RuntimeException x) {
					thrown = x; throw x;
				} catch (Error x) {
					thrown = x; throw x;
				} catch (Throwable x) {
					thrown = x; throw new Error(x);
				} finally {
					afterExecute(task, thrown); //对外扩展方法
				}
			} finally {
				task = null;
				w.completedTasks++;
				w.unlock();
			}
		}
		completedAbruptly = false;
	} finally {
		processWorkerExit(w, completedAbruptly);
	}
}

 3.5 线程池的监控

通过自定义线程池的方式,重写线程池里面的部分方法,从而可以实现对线程池的监控。

public class CustomizedExecutors {
    public static ExecutorService newFixedThreadPool(int nThreads) {
        // 使用我们自定义的线程池
        return new CustomizedThreadPool(nThreads, nThreads,
                0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<Runnable>());
    }
}

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

    @Override
    protected void beforeExecute(Thread t, Runnable r) {
        super.beforeExecute(t, r);
        // TODO 可以获取需要监控的指标和数据
    }

    @Override
    protected void afterExecute(Runnable r, Throwable t) {
        super.afterExecute(r, t);
        // TODO 通过重写可以获取需要监控的指标和数据
        System.out.println("初始线程数" + this.getPoolSize());
        System.out.println("核心线程数" + this.getCorePoolSize());
        System.out.println("正在执行的任务数" + this.getActiveCount());
        System.out.println("已经完成的任务数" + this.getCompletedTaskCount());
        System.out.println("任务总数" + this.getTaskCount());
    }
}

3.6 线程数设置

线程数量计算

最佳的线程数量是保持处理器在理想的利用率: 

\LARGE N_{threads} \LARGE = \LARGE N_{cpu} * \LARGE U_{cpu} * ( 1 + \large \frac{W}{C})

\large N_{cpu}: CPU的数量

\large U_{cpu}: CPU的使用率, 0 ≤ \large U_{cpu} ≤ 1 

\frac{W}{C}: 等待时间和计算时间的比;这个值越大代表等待时间越长,CPU利用率不高(IO密集型),所以可以增加线程数切换给其他线程做更多事情;如果这个值越小说明CPU利用率很高(CPU密集型),不应该设置过多线程,否则会增加上下文切换,带来性能损耗。

一般IO密集型线程数量设置为 2 * CPU core + 1

一般CPU密集型线程数量设置为 CPU core + 1

动态设置线程池大小

通过实时监控队列中任务堆积情况来判断是否需要扩容,还是需要回收部分线程,从而实现线程池大小的动态设置

public class ThreadPoolExecutorTest {
    // 扩容因子
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    static final int QUEUE_SIZE = 20; // 阻塞队列大小
    static int CORE_POOL_SIZE = 2; //默认核心线程数
    static int MAX_CORE_POOL_SIZE = 5; //默认最大线程数
    static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE, MAX_CORE_POOL_SIZE,
            60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(QUEUE_SIZE));
    static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        for (int i = 0; i < 20; i ++) {
            threadPoolExecutor.execute(()->{
                try {
                    Thread.sleep(100);
                    tryPresize();
                    System.out.println(Thread.currentThread().getName() + "执行一个任务");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
    }

    private static void tryPresize() {
        if (threadPoolExecutor.getQueue().size() > QUEUE_SIZE * DEFAULT_LOAD_FACTOR) {
            lock.lock(); //加锁保证修改线程池参数时线程安全性
            try {
                if (threadPoolExecutor.getQueue().size() > QUEUE_SIZE * DEFAULT_LOAD_FACTOR) {
                    printPoolStatus("increment before");
                    threadPoolExecutor.setCorePoolSize(threadPoolExecutor.getCorePoolSize() << 1);
                    threadPoolExecutor.setMaximumPoolSize(threadPoolExecutor.getMaximumPoolSize() << 1);
                    printPoolStatus("increment after");
                }
            } finally {
                lock.unlock();
            }
        } else if (threadPoolExecutor.getQueue().size() < (QUEUE_SIZE >> 1)) {
            lock.lock(); //加锁保证修改线程池参数时线程安全性
            try {
                if (threadPoolExecutor.getQueue().size() < (QUEUE_SIZE >> 1)) {
                    printPoolStatus("decrement before");
                    int resizeCorePoolSize = threadPoolExecutor.getCorePoolSize() >> 1;
                    int resizeMaxCorePoolSize = threadPoolExecutor.getMaximumPoolSize() >> 1;
                    threadPoolExecutor.setCorePoolSize(resizeCorePoolSize > CORE_POOL_SIZE ? resizeCorePoolSize : CORE_POOL_SIZE);
                    threadPoolExecutor.setMaximumPoolSize(resizeMaxCorePoolSize > MAX_CORE_POOL_SIZE ? resizeMaxCorePoolSize : MAX_CORE_POOL_SIZE);
                    printPoolStatus("decrement after");
                }
            } finally {
                lock.unlock();
            }
        }
    }

    private static void printPoolStatus(String name) {
        System.out.print(name + ": ");
        System.out.print("核心线程数=" + threadPoolExecutor.getCorePoolSize() + ", ");
        System.out.print("最大线程数=" + threadPoolExecutor.getMaximumPoolSize() + ", ");
        System.out.println("队列任务数量=" + threadPoolExecutor.getQueue().size());
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值