线程池
什么是线程池?
参考链接:https://baike.baidu.com/item/%E7%BA%BF%E7%A8%8B%E6%B1%A0/4745661
线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池线程都是后台线程。每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中。如果某个线程在托管代码中空闲(如正在等待某个事件),则线程池将插入另一个辅助线程来使所有处理器保持繁忙。如果所有线程池线程都始终保持繁忙,但队列中包含挂起的工作,则线程池将在一段时间后创建另一个辅助线程但线程的数目永远不会超过最大值。超过最大值的线程可以排队,但他们要等到其他线程完成后才启动。
为什么要用线程池?
ThreadPoolExecutor 的类关系
-
- ThreadPoolExecutor 是线程池的核心实现类,用来执行被提交的任务。
- ScheduledExecutorService 接口继承了 ExecutorService 接口,提供了带"周期执行"功能 ExecutorService;
- ScheduledThreadPoolExecutor是一个实现类,可以在给定的延迟后运行命令,或者定期执行命令。
- ScheduledThreadPoolExecutor 比 Timer 更灵活,功能更强大。
线程池的创建各个参数含义
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler)
corePoolSize
maximumPoolSize
keepAliveTime
workQueue
-
- 1)当线程池中的线程数达到 corePoolSize 后,新任务将在无界队列中等待,因此线程池中的线程数不会超过 corePoolSize。
- 2)由于 1,使用无界队列时 maximumPoolSize 将是一个无效参数。
- 3)由于 1 和 2,使用无界队列时 keepAliveTime 将是一个无效参数。
- 4)更重要的,使用无界 queue 可能会耗尽系统资源,有界队列则有助于防止资源耗尽,同时即使使用有界队列,也要尽量控制队列的大小在一个合适的范围。
所以我们一般会使用,ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue、PriorityBlockingQueue。
threadFactory
import cn.enjoyedu.tools.SleepTools; import java.util.Random; import java.util.concurrent.*; /** *类说明:自定义线程池中线程的创建方式 */ public class ThreadPoolAdv { static class Worker implements Runnable { private String taskName; private Random r = new Random(); public Worker(String taskName){ this.taskName = taskName; } public String getName() { return taskName; } @Override public void run(){ System.out.println(Thread.currentThread().getName() +" process the task : " + taskName); try { TimeUnit.MILLISECONDS.sleep(r.nextInt(100)*5); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) throws InterruptedException, ExecutionException { ExecutorService threadPool = new ThreadPoolExecutor(2, 4, 3, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(10), //TODO new ThreadPoolExecutor.DiscardOldestPolicy()); for (int i = 0; i <= 6; i++) { Worker worker = new Worker("worker " + i); System.out.println("A new task has been added : " + worker.getName()); threadPool.execute(worker); } } }
RejectedExecutionHandler
如何正确的定义线程池的大小?
定义线程池的大小目前没有特别规范的......
但是可以从任务特性方面考虑,进而合理的分配线程池的大小
-
- CPU的密集型:不超过(机器CPU核心数+1)
- 处理计算或者逻辑封装数据
- IO密集型:(机器CPU核心数*2)(推荐) netty的线程池的就是(机器CPU核心数*2) 当然,若是(机器CPU核心数*2)时,还很空闲,可以继续(机器CPU核心数*2,*3,*4等)
- 涉及到磁盘、网络等
- 混合型(既有CPU的密集型,又有IO密集型)
- 若CPU密集时间和IO密集型时间相差不大---推荐拆分
- 若CPU密集时间(1s),IO密集型时间(10min),相差太大---不推荐拆分---未知---未测---不敢妄言
- CPU的密集型:不超过(机器CPU核心数+1)
CPU核心数=Runtime.getRuntime().availableProcessors();
线程池使用步骤及代码示例
使用步骤
-
- ①创建线程池对象
- ②创建Runable或Callable实现类实例
- ③提交Runable或Callable实现类实例
- ④关闭线程池
代码示例
public List<String> packageStrings(List<String> list) throws InterruptedException, ExecutionException{ // 开始时间 long start = System.currentTimeMillis(); // 创建一个大小等于CPU核心数的线程池 ExecutorService exec = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); // 定义FutureTask任务集合 try { List<Future<String>> futures = new ArrayList<Future<String>>(); for (final String str : list) { Future<String> future = exec.submit(new Callable<String>() { @Override public String call() throws Exception { // 封装处理数据 return packageString(str); } }); futures.add(future); } list.clear(); for (Future<String> future : futures) { list.add(future.get()); } } finally { exec.shutdown();//关闭空闲线程 // exec.shutdownNow();//关闭所有线程 } //System.out.println(list.toString()); System.err.println("\n执行任务消耗了 :" + (System.currentTimeMillis() - start) + "毫秒"); return list; }
线程池的一些常用方法:
1.newCachedThreadPool
public static ExecutorService newCachedThreadPool():创建一个具有缓存功能的线程池,系统根据需要创建线程,这些线程将会被缓存在线程池中;
2.newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads):创建一个可重用的、具有固定线程数的线程池;
import java.util.Date; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors;
public class Test { public static void main(String[] args) { //ExecutorService executorService = Executors.newSingleThreadExecutor();//线程池中只有一个线程 ExecutorService executorService = Executors.newFixedThreadPool(2);//若该线程池中有两个线程 executorService.submit(new TimeThread()); executorService.submit(new CounterThread()); executorService.shutdown(); } }
class CounterThread extends Thread{ @Override public void run() { for(int i=1;i<4;i++) { System.out.println(i); try { sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } class TimeThread extends Thread{ @Override public void run() { for(int i=1;i<4;i++) { System.out.println(new Date()); try { sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }
相当于马厩里有两批马,此时刚好需要两匹马(只有两个线程处于就绪状态),两匹马一起被用,故线程交替输出
3.newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor():创建一个只有单线程的线程池,相当于调用newFixedThreadPool(int nThreads)方法时传入参数为1。
相当于马厩里有一批马,但是此时需要两匹马(有两个线程处于就绪状态),只有一个线程能用马,谁的优先权高谁获得CPU的资源执行程序,等到这个线程把所有的程序都执行完了才把马还回来,马厩里有空马,另一个线程才能用这匹马
4.newScheduledThreadPool
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize):创建具有指定线程数的线程池,它可以在指定延迟后执行线程任务。 corePoolSize指池中所保存的线程数,即使线程时空闲的也被保存在线程池内。
Ps:此方法创建的线程池会吞异常:若是在线程内不try-catry则不会抛异常
5.newSingleThreadScheduledExecutor
public static ScheduledExecutorService newSingleThreadScheduledExecutor():创建只有一个线程的线程池,它可以在指定延迟后执行线程任务。
Ps:此方法创建的线程池会吞异常:若是在线程内不try-catry则不会抛异常
6.newWorkStealingPool
public static ExecutorService newWorkStealingPool(int parallelism):创建持有足够的线程的线程池来支持给定的并行级别,该方法还会使用多个队列来减少竞争。
7.newWorkStealingPool
public static ExecutorService newWorkStealingPool():该方法是前一个方法的简化版本。
关于Executor及线程池图文逻辑
线程池的工作原理
Executor框架
Executor框架基本使用流程