线程池以及手写实现

前言

什么是线程池

存放若干个用于执行任务的线程的容器。

为什么要用线程池

性能

线程的创建和销毁是非常消耗系统资源的,每当我们要执行异步任务时,都创建一个新的线程,会带来大量额外的开销,并且线程的数量不宜过多,线程过多会增加CPU调度的压力,导致多线程程序反而性能下降。
线程池可以预先创建好若干个线程实例,随时准备执行任务。

安全

线程是非常珍贵的资源,CPU调度线程的能力是有限的,意味着线程的创建是有限的,一旦代码编写错误,不断的创建新线程,可能会导致系统崩溃。

统一管理

使用线程池可以方便的对线程进行统一管理,例如:停止接收任务并销毁所有线程。

内置线程池

为了方便开发人员使用,JDK提供了一些预定义的线程池,可以直接使用,如果不满足需求可以自定义一个线程池。

使用Executors类来快速的创建线程池。

newSingleThreadExecutor

创建一个单线程的线程池,任务会按顺序执行,任务提交时会阻塞,必须等待前一个任务执行完。

public static void main(String[] args) {
	ExecutorService executorService = Executors.newSingleThreadExecutor();
	for (int i = 0; i < 3; i++) {
		executorService.execute(()-> System.out.println(Thread.currentThread().getName()));
	}
	/**
	 * 输出:
	 * pool-1-thread-1
	 * pool-1-thread-1
	 * pool-1-thread-1
	 */
}

newFixedThreadPool

创建一个固定线程数的线程池,来不及执行的任务会被放到任务队列中。

Executors.newFixedThreadPool(2);

newCachedThreadPool

创建一个带缓存的线程池,线程的数量范围在0~Integer.MAX_VALUE,默认线程空闲60秒就会被回收。

使用时需要注意,一旦线程数量过多可能导致系统崩溃。

Executors.newCachedThreadPool();

newScheduledThreadPool

创建一个固定核心线程数的线程池,支持任务定时执行。

扫描二维码关注公众号,回复: 8714305 查看本文章
  • scheduleAtFixedRate
    上一个任务运行结束到下一个任务开始运行的时间间隔。

  • scheduleWithFixedDelay
    上一个任务开始运行到下一个任务开始运行的时间间隔。

public static void main(String[] args) {
	ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
	//延迟1s执行,每隔1秒输出当前时间
	executorService.scheduleWithFixedDelay(() -> {
		System.out.println(System.currentTimeMillis());
	}, 1,1, TimeUnit.SECONDS);
}

newSingleThreadScheduledExecutor

创建一个单线程的线程池,支持任务定时执行。

Executors.newSingleThreadScheduledExecutor();

newWorkStealingPool

创建一个“工作密取”型的线程池,基于ForkJoinPool实现,适用于处理很耗时的操作。

ForkJoinPool和其他线程池相比较为特殊,可以多了解一下。

自定义线程池

ThreadPoolExecutor

使用ThreadPoolExecutor可以构建一个自己的线程池。

构造器

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)
  • corePoolSize
    核心线程的大小,默认不会创建线程,当有任务需要执行时才会创建线程。
    线程达到核心线程数时,不会再创建线程,会将任务放到队列中。

  • maximumPoolSize
    达到核心线程数,且任务队列也放满时,会继续创建线程,直到线程数达到maximumPoolSize。

  • keepAliveTime
    设置线程空闲多少时间会被回收,当线程数超过corePoolSize才会生效。

  • unit
    keepAliveTime的时间单位。

  • workQueue
    任务来不及处理时,存放的任务队列。

  • threadFactory
    创建线程的工厂。

  • handler
    饱和策略,当线程数达到maximumPoolSize仍然来不及处理任务时采取的措施:丢弃、抛异常、记录日志等…

例子

public class MyThreadPool {

	//自定义饱和策略
	private static class MyRejectedExecutionHandler implements RejectedExecutionHandler{

		@Override
		public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
			System.out.println("任务丢弃,记录日志...");
		}
	}

	public static void main(String[] args) {
		//任务队列 容量为2
		ArrayBlockingQueue<Runnable> queue = new ArrayBlockingQueue<Runnable>(2);

		/**
		 * 构建自定义线程池
		 * 核心线程数:1
		 * 最大线程数:2
		 */
		ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 2, 60,
				TimeUnit.SECONDS, queue, new MyRejectedExecutionHandler());

		//并发时,最多接收4个任务,其他丢弃
		for (int i = 0; i < 5; i++) {
			executor.execute(() -> {
				SleepUtil.sleep(1000);
				System.out.println(Thread.currentThread().getName());
			});
		}
		/**
		 * 输出:
		 * 任务丢弃,记录日志...
		 * pool-1-thread-2
		 * pool-1-thread-1
		 * pool-1-thread-2
		 * pool-1-thread-1
		 */
	}
}

execute和submit

execute仅代表异步执行一个任务,没有返回值。

submit除了可以提交Runnable外,还可以提交Callable,Callble是支持返回结果的。

public static void main(String[] args) throws ExecutionException, InterruptedException {
	ExecutorService executorService = Executors.newFixedThreadPool(1);
	Future<Integer> future = executorService.submit(() -> {
		int sum = 0;
		for (int i = 0; i < 100000; i++) {
			sum += i;
		}
		return sum;
	});

	Integer result = future.get();
	System.out.println(result);//704982704
}

手写线程池

有了多线程的学习基础,可以自己尝试实现一个线程池。

MyThreadPool
/**
 * @Author: 潘
 * @Date: 2019/11/26 17:02
 * @Description: 自己实现线程池
 */
public class MyThreadPool {
	private int coreSize;//核心线程数
	private int maxSize;//最大线程池
	private final int MAX_WAIT_QUEUE_SIZE = 10;//等待队列最大容量
	private final LinkedBlockingQueue<Runnable> WAIT_QUEUE = new LinkedBlockingQueue<>(MAX_WAIT_QUEUE_SIZE);
	//内置锁
	private ReentrantLock lock = new ReentrantLock();

	private BlockingQueue<MyThread> freeQueue;//空闲队列
	private BlockingQueue<MyThread> workQueue;//工作队列
	//关闭标记
	private AtomicBoolean isShutdown = new AtomicBoolean(false);

	public MyThreadPool(int coreSize, int maxSize) throws InterruptedException {
		this.coreSize = coreSize;
		this.maxSize = maxSize;
		//初始化线程
		freeQueue = new LinkedBlockingQueue<>();
		for (int i = 0; i < coreSize; i++) {
			freeQueue.put(new MyThread());
		}
		workQueue = new LinkedBlockingQueue<>();
	}

	public void execute(Runnable run){
		if (isShutdown.get()) {
			return;
		}
		lock.lock();
		if (!freeQueue.isEmpty()) {
			//有空闲线程
			MyThread poll = freeQueue.poll();
			poll.setRunnable(run);
			workQueue.add(poll);
			lock.unlock();
			return;
		}
		//没有空余线程,放入等待队列
		if (WAIT_QUEUE.size() < MAX_WAIT_QUEUE_SIZE) {
			WAIT_QUEUE.add(run);
			lock.unlock();
			return;
		}
		if (freeQueue.size() + workQueue.size() < maxSize) {
			//没有空余线程,等待队列也满了,但是还没到最大线程数
			MyThread myThread = new MyThread();
			workQueue.add(myThread);
			myThread.setRunnable(run);
			lock.unlock();
			return;
		}
		lock.unlock();
		//极限,丢弃任务
		System.err.println("log:任务丢弃...");
	}

	//关闭
	public void shutdown() {
		isShutdown.set(true);
	}

	private class MyThread extends Thread{
		private Runnable runnable;
		void setRunnable(Runnable runnable){
			this.runnable = runnable;
			if (!isAlive()) {
				start();
			}else {
				LockSupport.unpark(this);
			}
		}

		@Override
		public void run() {
			while (true) {
				try {
					runnable.run();
				}catch (Exception e){
					e.printStackTrace();
				}finally {
					//当前任务执行完毕 等待队列是否有任务?
					while (!WAIT_QUEUE.isEmpty()) {
						WAIT_QUEUE.poll().run();
					}
					workQueue.remove(this);
					freeQueue.add(this);
					LockSupport.park();
				}
			}
		}
	}
}

//测试
class Client{
	public static void main(String[] args) throws InterruptedException {
		MyThreadPool myThreadPool = new MyThreadPool(1, 2);
		for (int i = 0; i < 20; i++) {
			new Thread(() -> {
				myThreadPool.execute(()->{
					SleepUtil.sleep(100);
					System.out.println(Thread.currentThread().getName());
				});
			}).start();
		}
	}
}

尾巴

构建线程池时,线程数不宜过多,一般来说:

  • 对于计算密集型应用,线程数约等于CPU核心数,不宜过多。
  • 对于IO密集型应用,线程数约为CPU核心数的2倍。

具体的数字需要结合压力测试给出的结果来决定。

发布了100 篇原创文章 · 获赞 23 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/qq_32099833/article/details/103264879