Java并发 之 线程池系列 (1) 让多线程再也不坑爹的线程池

lady_pool_shit


背景

线程池的来由

服务端的程序,例如数据库服务器和Web服务器,每次收到客户端的请求,都会建立一个线程来处理这些请求。html

建立线程的方式又不少,例如继承Thread类、实现Runnable或者Callable接口等。java

经过建立新的线程来处理客户端的请求,这种看起来很容易的方法,实际上是有很大弊端且有很高的风险的。git

俗话说,简单的路越走越困难,困难的路越走越简单,就是这个道理。github

建立和销毁线程,会消耗大量的服务器资源,甚至建立和销毁线程消耗的时间比线程自己处理任务的时间还要长。shell

因为启动线程须要消耗大量的服务器资源,若是建立过多的线程会形成系统内存不足(run out of memory),所以限制线程建立的数量十分必要。数据库

dogs_multithread_programming

什么是线程池

线程池通俗来说就是一个取出和放回提早建立好的线程的池子,概念上,相似数据库的链接池。编程

那么线程池是如何发挥做用的呢?缓存

实际上,线程池是经过重用以前建立好线程来处理当前任务,来达到大大下降线程频繁建立和销毁致使的资源消耗的目的。服务器

A thread pool reuses previously created threads to execute current tasks and offers a solution to the problem of thread cycle overhead and resource thrashing. Since the thread is already existing when the request arrives, the delay introduced by thread creation is eliminated, making the application more responsive.多线程

Thread Pool

背景总结

下面总结一下开篇对于线程池的一些介绍。

  1. 线程是程序的组成部分,能够帮助咱们搞事情。
  2. 多个线程同时帮咱们搞事情,能够经过更大限度地利用服务器资源,用来大大提升咱们搞事情的效率。
  3. 咱们建立的每一个线程都不是省油的灯,线程越多就会占用越多的系统资源,所以小弟虽好使但不要贪多哦,在有限的系统资源下,线程并非“韩信点兵,多多益善”的,要限制线程的数量。请记住这一条,由于下面“批判”Java提供的线程池建立解决方案的时候,这就是“罪魁祸首”。
  4. 建立和销毁线程会耗费大量系统资源,就像大佬招募和遣散小弟,都是要大费周章的。所以聪明的大佬就想到了“池”,把线程缓存起来,用的时候拿出来不用的时候还放回去,这就能够既享受多线程的乐趣,又能够避免使用多线程的痛苦了。

但到底怎么使用线程池呢?线程池真的这么简单好用吗?线程池使用的过程当中有没有什么坑?

不要着急,下面就结合具体的示例,跟你讲解各类使用线程池的姿式,以及这些姿式爽在哪里,痛在哪里。

准备好纸巾,咳咳...,是笔记本,涛哥要跟你开讲啦!

用法

经过Executors建立线程池

Executors及其服务的类

java.util.concurrent.Executors是JDK的并发包下提供的一个工厂类(Factory)和工具类(Utility)。

Executors提供了关于Executor, ExecutorService, ScheduledExecutorService, ThreadFactoryCallable相关的工厂方法和工具方法。

Executor是一个执行提交的Runnable Tasks的对象,它有一个execute方法,参数是Runnable。当执行execute方法之后,会在将来某个时间,经过建立线程或者使用线程池中的线程的方式执行参数中的任务。用法以下:

Executor executor = anExecutor;
executor.execute(new RunnableTask1());
executor.execute(new RunnableTask2());
复制代码

ExecutorService继承了Executor,并提供了更多有意思的方法,好比shutdown方法会让ExecutorService拒绝建立新的线程来执行task。

Executors经常使用的几个方法

//建立固定线程数量的线程池
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10);

//建立一个线程池,该线程池会根据须要建立新的线程,但若是以前建立的线程可使用,会重用以前建立的线程
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

//建立一个只有一个线程的线程池
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();

复制代码

一个线程池的例子

下面我就建立5个Task,并经过一个包含3个线程的线程池来执行任务。咱们一块儿看下会发生什么。

Github 完整代码: 一个线程池的例子

ThreadPoolExample1就是咱们的测试类,下面全部的内部类、常量和方法都写在这个测试类里。

package net.ijiangtao.tech.concurrent.jsd.threadpool;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolExample1 {

}
复制代码

任务

Task内部类执行了两次for循环,并在每次循环执行结束之后 sleep 1秒钟。

// Task class to be executed (Step 1)
static class Task implements Runnable {

	private String name;

	public Task(String s) {
		name = s;
	}

	// Prints task name and sleeps for 1s
	// This Whole process is repeated 2 times
	public void run() {
		try {
			for (int i = 0; i <= 1; i++) {
				if (i == 0) {
					//prints the initialization time for every task
					printTimeMsg("Initialization");
				} else {
					// prints the execution time for every task
					printTimeMsg("Executing");
				}
				Thread.sleep(1000);
			}
			System.out.println(name + " complete");
		} catch (InterruptedException e) {
				e.printStackTrace();
		}
	}

	private void printTimeMsg(String state) {
		Date d = new Date();
		SimpleDateFormat ft = new SimpleDateFormat("hh:mm:ss");
		System.out.println(state+" Time for"+ " task name - " + name + " = " + ft.format(d));
	}
}
复制代码

池子

建立一个固定线程数的线程池。

// Maximum number of threads in thread pool
static final int MAX_T = 3;
// creates a thread pool with MAX_T no. of
// threads as the fixed pool size(Step 2)
private static final ExecutorService pool = Executors.newFixedThreadPool(MAX_T);
复制代码

测试

建立5个任务,并经过线程池的线程执行这些任务。

public static void main(String[] args) {
	 // creates five tasks
	 Runnable r1 = new Task("task 1");
	 Runnable r2 = new Task("task 2");
	 Runnable r3 = new Task("task 3");
	 Runnable r4 = new Task("task 4");
	 Runnable r5 = new Task("task 5");

	 // passes the Task objects to the pool to execute (Step 3)
	 pool.execute(r1);
	 pool.execute(r2);
	 pool.execute(r3);
	 pool.execute(r4);
	 pool.execute(r5);

	 // pool shutdown ( Step 4)
	 pool.shutdown();
}
复制代码

执行结果以下。

Initialization Time for task name - task 1 = 12:39:44
Initialization Time for task name - task 2 = 12:39:44
Initialization Time for task name - task 3 = 12:39:44
Executing Time for task name - task 3 = 12:39:45
Executing Time for task name - task 1 = 12:39:45
Executing Time for task name - task 2 = 12:39:45
task 2 complete
Initialization Time for task name - task 4 = 12:39:46
task 3 complete
Initialization Time for task name - task 5 = 12:39:46
task 1 complete
Executing Time for task name - task 5 = 12:39:47
Executing Time for task name - task 4 = 12:39:47
task 5 complete
task 4 complete
复制代码

说明

从输出的结果咱们能够看到,5个任务在包含3个线程的线程池执行。

  1. 首先会有3个任务(task 1,task 2,task 3)得到线程资源并发执行;
  2. (task 2)执行成功之后,让出线程资源,(task 4)开始执行;
  3. (task 3)执行成功之后,让出线程资源,(task 5)开始执行;
  4. 最终,5个任务都执行结束,线程池将线程资源回收。

因为线程的执行有必定的随机性,以及不一样机器的资源状况不一样,每次的执行结果,可能会有差别。

下面是我第二次执行的结果。

Initialization Time for task name - task 1 = 12:46:33
Initialization Time for task name - task 3 = 12:46:33
Initialization Time for task name - task 2 = 12:46:33
Executing Time for task name - task 2 = 12:46:34
Executing Time for task name - task 3 = 12:46:34
Executing Time for task name - task 1 = 12:46:34
task 3 complete
task 2 complete
task 1 complete
Initialization Time for task name - task 4 = 12:46:35
Initialization Time for task name - task 5 = 12:46:35
Executing Time for task name - task 4 = 12:46:36
Executing Time for task name - task 5 = 12:46:36
task 5 complete
task 4 complete
复制代码

task 1 2 3 得到线程资源,task 4 5排队等待:

task 1 2 3 得到线程资源,task 4 5排队等待

task 1 2 3 执行结束,task 4 5得到线程资源,线程池中有一个线程处于空闲状态:

task 1 2 3 执行结束,task 4 5得到线程资源

但规律是相同的,那就是线程池会将本身的线程资源贡献出来,若是任务数超出了线程池的线程数,就会阻塞并排队等待有可用的线程资源之后执行。

也就是线程池会保证你的task在未来(Future)的某个时间执行,但并不能保证什么时间会执行。

相信你如今对于ExecutorServiceinvokeAll方法,能够执行一批task并返回一个Future集合,就会有更深刻的理解了。

List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException
复制代码

经过ExecutorService线程池执行task的过程以下图所示,超出线程池线程数量的task将会在BlockingQueue排队等待得到线程资源的机会。

关于并发编程中的Futrue,笔者有一篇文章(Java并发编程-Future系列之Future的介绍和基本用法)专门介绍,请经过下面任意的连接移步欣赏:

总结

本教程带领你们了解了线程池的来由、概念和基本用法,相信你们看完,之后就再也不只会傻傻地new Thread了。

本节只是线程池的入门,下面会介绍关于线程池的更多武功秘籍,但愿你们持续关注,有所获益。


Links

文章友链

相关资源

Concurrent-ThreadPool-线程池拒绝策略RejectedExecutionHandler

Concurrent-ThreadPool-ThreadPoolExecutor里面4种拒绝策略

Concurrent-ThreadPool-线程池ThreadPoolExecutor构造方法和规则

Concurrent-ThreadPool-线程池的成长之路

Concurrent-ThreadPool-LinkedBlockingQueue和ArrayBlockingQueue的异同

Concurrent-ThreadPool-最佳线程数总结

Concurrent-ThreadPool-最佳线程数

Concurrent-ThreadPool-Thread Pools in Java

Concurrent-ThreadPool-java-thread-pool

Concurrent-ThreadPool-thread-pool-java-and-guava

Concurrent-ThreadPool-ijiangtao.net

相关文章
相关标签/搜索