java线程池
什么是线程池?
线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。
为什么要用线程池?
线程池可以对开发带来以下好处:
- 降低资源消耗
- 提高相应速度
- 提高线程的可管理性
线程池的组成
- 线程的容器和管理器
- 工作线程
- 任务接口
- 任务的容器
处理流程
- 线程池首先判断核心线程池里的线程是否都在执行任务,如果不是,则创建一个新的工作线程来执行任务;如果核心线程池里的线程都在执行任务,则进入下个流程。
- 线程池然后判断工作队列是否已满,如果工作队列没满,则将新提交的任务存储在这个工作队列里,如果工作队列已满,则进入下个流程。
- 线程池最后判断线程池的线程是否都处于工作状态,如果没有,则新建一个新的工作线程来执行任务,如果已满,则交给饱和策略来处理这个任务。
public class MyThreadPool { // 默认的线程个数 private int work_num = 5; // 线程的容器 private WorkThread[] workThreads; // 任务队列 private List<Runnable> taskQueue = new LinkedList<>(); public MyThreadPool(int work_num) { this.work_num = work_num; this.workThreads = new WorkThread[this.work_num]; for (int i = 0; i < this.work_num; i ++) { this.workThreads[i] = new WorkThread(); this.workThreads[i].start(); } } // 提交任务的接口 public void execute(Runnable task) { synchronized (this.taskQueue) { this.taskQueue.add(task); this.taskQueue.notify(); } } // 销毁线程池 public void destroy() { System.out.println("ready stop pool ..."); for (int i = 0; i < this.work_num; i ++) { this.workThreads[i].stopWork(); this.workThreads[i] = null; //加速垃圾回收 } this.taskQueue.clear(); } // 工作线程 private class WorkThread extends Thread { private volatile boolean on = true; @Override public void run() { Runnable r = null; try { while (this.on && !this.isInterrupted()) { synchronized (taskQueue) { while (this.on && this.isInterrupted() && taskQueue.isEmpty()) { taskQueue.wait(1000); } if (this.on && !this.isInterrupted() && !taskQueue.isEmpty()) { r = taskQueue.remove(0); } } if (null != r) { System.out.println(this.getId() + " ready execute ..."); r.run(); } r = null; // 加速垃圾回收 } } catch (InterruptedException e) { e.printStackTrace(); } } public void stopWork() { this.on = false; this.interrupt(); } } }
public class MyThreadPoolTest { public static void main(String[] args) throws InterruptedException { // 创建3个线程的线程池 MyThreadPool pool = new MyThreadPool(3); pool.execute(new MyTask("TestA")); pool.execute(new MyTask("TestB")); pool.execute(new MyTask("TestC")); pool.execute(new MyTask("TestD")); pool.execute(new MyTask("TestE")); System.out.println(pool); Thread.sleep(3000); pool.destroy(); System.out.println(pool); } // 任务类 static class MyTask implements Runnable { private String name; private Random r = new Random(); public MyTask(String name) { this.name = name; } public String getName() { return this.name; } @Override public void run() { try { Thread.sleep(r.nextInt(1000) + 2000); } catch (InterruptedException e) { System.out.println(Thread.currentThread().getId() + " sleep InterruptedException:" + Thread.currentThread().isInterrupted()); } System.out.println("任务 " + this.name + " 完成"); } } }
输出:
12 ready execute ... 10 ready execute ... org.eocencle.thread.pool.MyThreadPool@7852e922 11 ready execute ... 任务 TestC 完成 11 ready execute ... 任务 TestA 完成 12 ready execute ... 任务 TestB 完成 ready stop pool ... 11 sleep InterruptedException:false 任务 TestD 完成 12 sleep InterruptedException:false 任务 TestE 完成 org.eocencle.thread.pool.MyThreadPool@7852e922
上面是我们自己实现的一个线程池,可以看到我们处理了InterruptException异常,保证每个正在运行的线程都能在关闭时执行完。
ThreadPoolExecutor
执行顺序
- 如果当前运行的线程小于corePoolSize,则创建新线程来执行任务(注意:执行这一步骤需要获取全局锁)。
- 如果运行的线程等于或多于corePoolSize,则将任务加入BlockingQueue。
- 如果无法将任务加入BlockingQueue(队列已满),则创建新的线程处理任务(注意:执行这一步骤需要获取全局锁)。
- 如果创建新线程将使当前运行的线程超出maxmumPoolSize,任务将被拒绝,并调用RejectedExecutionHandler.rejectedExecution()方法。
构造方法
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
参数:
- corePoolSize:线程池中的核心线程数,当提交一个任务时,线程池创建一个新线程执行任务,直到当前线程数等于corePoolSize。如果当前线程数为corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行。如果执行了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有核心线程。
- maxmumPoolSize:线程池中允许的最大线程数。如果当前阻塞队列满了,且继续提交任务,则创建新的线程执行任务,前提是当前线程数小于maxmumPoolSize。
- keepAliveTime:线程空闲时的存活时间,即当线程没有任务执行时,继续存活的时间。默认情况下,该参数只在线程数大于corePoolSize时才有用。
- unit:keepAliveTime的时间单位。
- workQueue:必须是BlockingQueue阻塞队列。当线程池中的线程数超过它的corePoolSize的时候,线程会进入阻塞队列进行阻塞等待。通过workQueue,线程池实现了阻塞功能。
- threadFactory:创建线程的工厂,通过自定义的线程工厂可以给每个新建的线程设置一个具有识别度的线程名。Executors静态工厂里默认的threadFactory,线程的命名规则是“pool-数字-thread-数字”。
- handler:线程池的饱和策略。当阻塞队列满了,且没有空闲的工作线程,如果继续提交任务必须采取一种策略处理该任务,线程池提供了4中策略:
- AbortPolicy:直接抛出异常,默认策略。
- CallerRunsPolicy:用调用者所在的线程执行任务。
- DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务。
- DiscardPolicy:直接丢弃任务。
- 也可以自定义策略,直接实现RejectedExecutionHandler接口即可。
下面是创建一个线程池
public class ThreadPoolExecutorTest { public static class Task implements Runnable { private String name; public Task(String name) { this.name = name; } public void run() { System.out.println(this.name + ":" + Thread.currentThread().getName()); try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { ThreadPoolExecutor tpe = new ThreadPoolExecutor(2, 4, 1, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3)); Thread.currentThread().setName("main"); for (int i = 0; i < 10; i ++) { System.out.println("---线程" + i + "---"); tpe.execute(new Task("线程" + i)); } tpe.shutdown(); } }
输出:
---线程0--- ---线程1--- 线程0:pool-1-thread-1 ---线程2--- ---线程3--- 线程1:pool-1-thread-2 ---线程4--- ---线程5--- ---线程6--- 线程5:pool-1-thread-3 ---线程7--- 线程6:pool-1-thread-4 Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task org.eocencle.thread.ThreadPoolExecutorTest$Task@33909752 rejected from java.util.concurrent.ThreadPoolExecutor@55f96302[Running, pool size = 4, active threads = 4, queued tasks = 3, completed tasks = 0] at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063) at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830) at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379) at org.eocencle.thread.ThreadPoolExecutorTest.main(ThreadPoolExecutorTest.java:42) 线程2:pool-1-thread-1 线程3:pool-1-thread-2 线程4:pool-1-thread-4
线程0、1放进线程池的一刻便由核心线程开始执行,线程2、3、4被放进有节队列里,线程5、6开始创建线程执行,当添加线程7抛出了RejectedExecution饱和异常,至此之后打断了任务添加。
上面的线程池使用默认AbortPolicy策略,抛出异常终止添加任务,下面我们修改一下饱和策略:
ThreadPoolExecutor tpe = new ThreadPoolExecutor(2, 4, 1, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3), new ThreadPoolExecutor.CallerRunsPolicy());
输出:
---线程0--- ---线程1--- 线程0:pool-1-thread-1 ---线程2--- 线程1:pool-1-thread-2 ---线程3--- ---线程4--- ---线程5--- ---线程6--- 线程5:pool-1-thread-3 ---线程7--- 线程7:main 线程6:pool-1-thread-4 线程2:pool-1-thread-1 线程3:pool-1-thread-2 ---线程8--- 线程4:pool-1-thread-3 ---线程9--- 线程8:pool-1-thread-4 线程9:pool-1-thread-1
上面输出的结果表明,当线程达到最大线程数,队列里也已满时,在到第八个线程时,使用主线程执行了该任务。
ThreadPoolExecutor tpe = new ThreadPoolExecutor(2, 4, 1, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3), new ThreadPoolExecutor.DiscardOldestPolicy());
输出:
---线程0--- ---线程1--- 线程0:pool-1-thread-1 线程1:pool-1-thread-2 ---线程2--- ---线程3--- ---线程4--- ---线程5--- ---线程6--- 线程5:pool-1-thread-3 ---线程7--- 线程6:pool-1-thread-4 ---线程8--- ---线程9--- 线程7:pool-1-thread-2 线程8:pool-1-thread-1 线程9:pool-1-thread-3
DiscardOldestPolicy策略当达到最大线程数时,再添加任务则把队列最前面的任务踢出,再添加新任务。
ThreadPoolExecutor tpe = new ThreadPoolExecutor(2, 4, 1, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3), new ThreadPoolExecutor.DiscardPolicy());
输出:
---线程0--- ---线程1--- 线程0:pool-1-thread-1 ---线程2--- ---线程3--- 线程1:pool-1-thread-2 ---线程4--- ---线程5--- ---线程6--- 线程5:pool-1-thread-3 ---线程7--- 线程6:pool-1-thread-4 ---线程8--- ---线程9--- 线程2:pool-1-thread-2 线程4:pool-1-thread-4 线程3:pool-1-thread-1
DiscardPolicy策略则是直接抛弃超出最大线程数任务
ThreadPoolExecutor tpe = new ThreadPoolExecutor(2, 4, 1, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3), new RejectedExecutionHandler() { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { System.out.println("饱和异常"); } });
输出:
---线程0--- ---线程1--- 线程0:pool-1-thread-1 ---线程2--- ---线程3--- 线程1:pool-1-thread-2 ---线程4--- ---线程5--- ---线程6--- 线程5:pool-1-thread-3 ---线程7--- 饱和异常 ---线程8--- 线程6:pool-1-thread-4 饱和异常 ---线程9--- 饱和异常 线程2:pool-1-thread-1 线程3:pool-1-thread-2 线程4:pool-1-thread-3
自定义策略
关闭线程池
- shutdown():以interrupt方法来终止线程。
- shutdownNow():尝试停止所有正在执行的线程。
合理配置线程池
线程分配
任务分为计算密集型、IO密集型和混合型
计算密集型:计算机的CPU数量或者CPU数量+1(应付也缺失)
IO密集型:计算机的CPU数 * 2
混合型:拆分为计算密集型和IO密集型
java获取当前机器CPU核心数:
Runtime.getRuntime().availableProcessors();
尽量使用有界队列,不要使用无界队列。