本文共 6309 字,大约阅读时间需要 21 分钟。
在Java中,如果每个请求到达就创建一个新的线程,创建和销毁线程花费的世界和消耗的系统资源都相当大,甚至可能要比在实际的用户请求的时间和资源要多的多。
如果在一个JVM里创建太多线程,可能会使得系统由于过度消耗内存或切换过度而导致系统资源不足。
为了解决这些问题,就有了线程池的该娘,线程池的核心逻辑就是提前创建好若干个线程放在一个容器中。如果有任务需要处理则将任务之间分配给线程池中的线程来执行即可,任务处理完以后,这个线程不会销毁,而是等待后续分配任务。同时通过线程池来重复管理线程还可以避免创建大量线程增加开销。
线程池的优点:降低创建线程和销毁线程的性能开销;提高响应速度,当有新任务需要执行是不需要等待线程创建就可以立马执行;合理的设置线程池大小可以避免因为线程数超过硬件资源瓶颈带来的问题。
ThreadPoolExecutor
线程池技术在并发时会使用到,Java中的线程池的使用是通过调用ThreadPoolExecutor来实现的。
//----------------- 参数说明 ----------------- // new ThreadPoolExecutor(corePoolSize, //初始化线程数 // maximumPoolSize, //最大线程数 // keepAliveTime, //线程活跃时间 // unit, //线程活跃时间单位(秒、分) // workQueue, //任务队列 // threadFactory, //创建新线程使用的工厂 // handler); //拒绝策略,如线程池满了 |
有界队列和无界队列的选择:
有界队列:有任务需要执行,如果线程池实际线程数小于corePoolSize,则优先创建线程。
若大于CorePoolSize,则会将任务加入队列中。
若队列已满,则在总线程数不大于maximumPoolSize的前提下创建新的线程池。
若线程数大于maximumPoolSize,则执行拒绝策略,或其他自定义方式。
有界队列(LinkedBlockingQueue):与有界队列相比,除非系统资源耗尽,否则无界队列的任务队列不存在任务入队失败的情况。
当有新的任务到来,系统线程数小于corePoolSize,则新建线程执行任务,在达到了corePoolSize后,就不会继续增加。
若后续依然有新的任务加入,而没有空间的线程资源,则任务进入队列等待,若任务创建和处理速度差异很大,无界队列会快速增长,直到耗尽系统内存。
无界队列是以corePoolSize为瓶颈,maxPoolSize没有起作用的。
JDK自带的拒绝策略:
AbortPolicy:直接抛出异常,系统正常工作。
CallerRunsPolicy:只要线程池未关闭,该策略直接在调用者线程中,运行当前被丢弃的任务。
DiscardOldestPolicy:对其最老的一个请求,尝试再次提交当前任务。
DiscardPolicy:丢弃无法处理的任务,不给予如何处理。
自定义线程池拒绝策略:实现RejectedExecutionHandler接口。
import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.RejectedExecutionHandler; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit;
/** * */ public class P01ThreadPoolExecutor { public static void main(String[] args) throws InterruptedException {
/* * 有界队列和无界队列的选择: * 有界队列: * 有任务需要执行,如果线程池实际线程数小于corePoolSize,则优先创建线程。 * 若大于CorePoolSize,则会将任务加入队列中。 * 若队列已满,则在总线程数不大于maximumPoolSize的前提下创建新的线程池。 * 若线程数大于maximumPoolSize,则执行拒绝策略,或其他自定义方式。 * * 有界队列(LinkedBlockingQueue): * 与有界队列相比,除非系统资源耗尽,否则无界队列的任务队列不存在任务入队失败的情况。 * 当有新的任务到来,系统线程数小于corePoolSize,则新建线程执行任务,在达到了corePoolSize后,就不会继续增加。 * 若后续依然有新的任务加入,而没有空间的线程资源,则任务进入队列等待,若任务创建和处理速度差异很大,无界队列会快速增长,直到耗尽系统内存。 * 无界队列是以corePoolSize为瓶颈,maxPoolSize没有起作用的。 */
/* * JDK自带的拒绝策略: * AbortPolicy:直接抛出异常,系统正常工作。 * CallerRunsPolicy:只要线程池未关闭,该策略直接在调用者线程中,运行当前被丢弃的任务。 * DiscardOldestPolicy:对其最老的一个请求,尝试再次提交当前任务。 * DiscardPolicy:丢弃无法处理的任务,不给予如何处理。 * * 自定义线程池拒绝策略:实现RejectedExecutionHandler接口。 */
ThreadPoolExecutor pool = new ThreadPoolExecutor(1, //初始化线程数 2, //最大线程数 30, //活跃时间 TimeUnit.SECONDS //活跃时间单位 ,new ArrayBlockingQueue<Runnable>(3) //有界队列 // ,new LinkedBlockingQueue<Runnable>() //无界队列 // ,new DefTestReject() //自定义拒绝策略 // ,new AbortPolicy() ); pool.execute(new TestTask(1, "A")); pool.execute(new TestTask(2, "B")); pool.execute(new TestTask(3, "C")); pool.execute(new TestTask(4, "D")); pool.execute(new TestTask(5, "E")); pool.execute(new TestTask(6, "F"));
Thread.sleep(1000);
System.out.println("队列长度:" + pool.getQueue().size());
Thread.sleep(2000);
System.out.println("队列长度:" + pool.getQueue().size());
//1、首次运行 //2、打开自定义拒绝策略,打开注释pool.execute(new TestTask(6, "F"));,运行可以看到拒绝策略起作用了 //3、打开无界队列,每次运行都是1个运行,而不是按照maximumPoolSize数量来运行 //4、打开自定义拒绝策略和无界队列运行,会发现拒绝策略没有起来。 //5、注释自定义拒绝策略,打开JDK拒绝策略和有界队列,会发现起作用了。
pool.shutdown();
} } class TestTask implements Runnable {
private int id;
private String name;
public TestTask(int id, String name) { super(); this.id = id; this.name = name; }
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
@Override public void run() { // System.out.println("当前id:" + this.id + ", " + this.name + ",开始"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("当前id:" + this.id + ", " + this.name + ",结束"); } } /** * 自定义线程池拒绝策略 */ class DefTestReject implements RejectedExecutionHandler { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { System.out.println("任务加不进去线程池.........记录下日志,发送邮件/短信通知开发人员。"); } } |
Executors
为了更好的控制多线程,JDK提供了一套线程矿机Executor,让开发有效的进行线程控制。
这些类都是在java.util.concurrent包中,是JDK并发包中的核心,其中有一个比较重要的类Executors。
Executors扮演的是线程工厂的角色,在使用中通常也是使用Executors可以创建特定功能的线程池。
newFixedThreadPool():创建一个线程的线程池,线程池有空闲,则立即执行。线程池没有空闲的情况下,则会暂缓在一个任务队列中。
newSingleThreadExecutor():创建一个线程的线程池,线程池有空闲,则立即执行。线程池没有空闲的情况下,则会暂缓在一个任务队列中。
newCachedThreadPool():返回一个可根据实际情况调整线程个数的线程池,不限制最大线程数量(内存)。线程池有空闲,则立即执行。如果没有任务则不创建线程。并且每一个空闲线程在60秒内自动回收。
newScheduledThreadPool():可以指定线程数量。
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit;
/** * */ public class P02Executors { public static void main(String[] args) { /* * newFixedThreadPool():是返回一个固定数量的线程池,该方法的线程数始终不变, * 但有一个任务提交时,线程池空闲,则立即执行;线程没有空闲的情况下,则会暂缓在一个任务队列中等待有空闲的线程去执行。 * * 底层源码:new ThreadPoolExecutor(nThreads, nThreads, * 0L, TimeUnit.MILLISECONDS, * new LinkedBlockingQueue<Runnable>()); * */ ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(5);
/* * newSingleThreadExecutor():创建一个线程的线程池,线程池有空闲,则立即执行。 * 线程池没有空闲的情况下,则会暂缓在一个任务队列中。 * * 底层源码:new ThreadPoolExecutor(1, 1, * 0L, TimeUnit.MILLISECONDS, * new LinkedBlockingQueue<Runnable>()) */ ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
/* * newCachedThreadPool():返回一个可根据实际情况调整线程个数的线程池,不限制最大线程数量(内存)。 * 线程池有空闲,则立即执行。如果没有任务则不创建线程。并且每一个空闲线程在60秒内自动回收。 * * 底层源码:new ThreadPoolExecutor(0, Integer.MAX_VALUE, * 60L, TimeUnit.SECONDS, * new SynchronousQueue<Runnable>()); */ ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
/* * newScheduledThreadPool(1):可以指定线程的数量。 * * 底层源码: * new ScheduledThreadPoolExecutor(corePoolSize); * super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,new DelayedWorkQueue()); * class ScheduledThreadPoolExecutor extends ThreadPoolExecutor */ ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(1); //开始等待3秒后执行,一秒执行一次 newScheduledThreadPool.scheduleWithFixedDelay(new Thread(new Runnable() { @Override public void run() { System.out.println("run"); } }), 3, 1, TimeUnit.SECONDS);
/* * 方法区别: * submit():可以传入实现Callable接口或Runnable接口实例对象,另外有返回值。 * execute():只能传入Runnable接口,另外无返回值 * * submit()可以看Future设计模式博客文章。 * */ } } |
Executors的submit()和execute()方法的区别:
submit():可以传入实现Callable接口或Runnable接口实例对象,另外有返回值。可以看Future设计模式博客文章。
execute():只能传入Runnable接口,另外无返回值
转载地址:http://ymmxi.baihongyu.com/