简单认识线程池
1 简介
对于传统使用多线程的模式会有以下问题:
多线程运行时间,系统不断的启动和关闭新线程,会过渡消耗系统资源
过度切换线程的危险,从而可能导致系统资源的崩溃
池化技术
不直接创建具体的资源,而是创建一个池,在池里面创建具体的资源
以前是直接把任务交给具体的资源,而现在把资源交给池,池就会让空闲的资源去执行任务,任务执行完了以后,资源并不会销毁,而是停留池里面,等待下一个任务来执行
线程池好处
降低系统资源消耗、方便线程并发数的管控、可以延时定时线程池
2 工作原理
一个线程同时只能执行一个任务,但可以同时向一个线程池提交多个任务。
线程池中的线程不需要start,只要提交(execute)上去,线程池会在内部寻找是否有空闲的线程,如果有,则将任务交给某个空闲的线程,如果可执行的线程已经满了,那么剩下的任务会进入等待,直到其中某个线程执行结束将线程让出。
线程池工作的本质就是将要执行的任务添加到队列中,然后线程池寻求空闲的线程来执行队列里面的任务。
3 工作队列
BlockingQueue
双缓冲队列,内部使用两条队列,一个存储,一个取出,允许两个线程同时向队列操作。在保证并发安全的同时,提高了队列 的存取效率。
public interface BlockingQueue<E> extends Queue<E>
由于继承了队列接口,所以遵循了队列的原则,即先进先出(FIFO)阻塞队列继承了Queue队列遵循先进先出原则 队列提供几种基本的操作: 添加元素(队尾) 移除元素(队头) 取出队头元素(不移除) 每种操作都有两个方法,一种有可能抛异常,一种返回操作成功或 失败。 其实现类如下:
ArrayBlockingQueue 采用数组实现,capacity是构建时传入的参数,表示生成的BlockingQueue的大小,其所含的对象是FIFO排序
public ArrayBlockingQueue(int capacity) { this(capacity, false); }
LinkedBlockingQueue 采用双向链表实现,大小不固定的。如果用无参构造,则默认大小是Integer.MAX_VALUE,若传入capacity且capacity合法,则自定义生成的BlockingQueue的大小,其所含的对象是FIFO排序
public LinkedBlockingDeque() { this(Integer.MAX_VALUE); } public LinkedBlockingDeque(int capacity) { if (capacity <= 0) throw new IllegalArgumentException(); this.capacity = capacity; }
PriorityBlockingQueue 类似于LinkedBlockingQueue,不过所含对象不是FIFO排序,而是依据对象的自然顺序或者构造函数的Comparator决定。
public PriorityBlockingQueue() { this(DEFAULT_INITIAL_CAPACITY, null); }
SynchronousQueue 是一种特殊的BlockingQueue,这个容器相当于通道,本身不存储元素,每次想要进行删除操作都要先有插入操作,每次想要进行插入操作都要先有等待删除操作,在多任务队列,这是最快的处理任务方式
public SynchronousQueue() { this(false); }
4 线程池的使用
创建对象:ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize,//核心线程数 int maximumPoolSize,//最大线程数(即核心线程数+其他线程数的总和) long keepAliveTime,//除了核心线程之外其他线程的最长存活时间 TimeUnit unit,//时间单位 BlockingQueue<Runnable> workQueue) {//工作队列 this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), defaultHandler); }
//基本练习
ThreadPoolExecutor executor = new ThreadPoolExecutor(2,6,2, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
//提交任务去执行
Runnable t1 = ()-> System.out.println(Thread.currentThread().getName());
Runnable t2 = ()-> System.out.println(Thread.currentThread().getName());
Runnable t3 = ()-> System.out.println(Thread.currentThread().getName());
executor.execute(t1);
executor.execute(t2);
executor.execute(t3);
//运行到此处main线程不会关闭,线程池中还有两个核心线程没有执行结束
//关闭线程池,等到里面所有的线程空闲了才会关闭
//如果有线程正在执行,那就等待执行完毕再关闭
executor.shutdown();
//showdown底层使用interrupt中断线程
5 常用线程池
java通过Executors提供了四种线程池供我们直接使用:
newCachedThreadPool
重复使用线程,一旦线程空闲,就不会创建,而是使用上一个任务遗留的线程执行
由源码可知:创建的都是非核心线程,最大线程数为Integer.MAX_VALUE,空闲线程存活时间是1分钟
有大量任务需要执行的情况下用这个
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
newFixedThreadPool
该线程池一旦创建,就会创建指定size的核心线程
由源码可知:核心线程数和最大线程相同,且空闲线程存活时间为0L
可以固定任务数量的情况下用这个
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
newSingleThreadExecutor
线程池里只创建了一个线程,这样就可以保证按照顺序执行任务
由源码可知:该线程池只创建了一个核心线程,且最大线程数为 1,空闲线程存活时间为 0L
有需求顺序执行任务时用这个
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }
newScheduledThreadPool
增加了延迟执行,和周期性执行
由源码可知:创建的是ScheduledThreadPoolExecutor,返回ScheduledExecutorService对象,向上追溯源码可知,创建了所传参数个核心线程,最大线程数是Integer.MAX_VALUE,空闲线程存活时间为 0
有需求周期性执行任务时用这个
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) { return new ScheduledThreadPoolExecutor(corePoolSize); } public ScheduledThreadPoolExecutor(int corePoolSize) { super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue()); }//super是ThreadPoolExecutor
测试延迟执行和周期性执行的功能
ScheduledExecutorService service = Executors.newScheduledThreadPool(5);
Runnable runnable = ()-> System.out.println(Thread.currentThread().getName());
//正常执行
service.execute(runnable);
//延迟执行:第二个和第三个参数分别为延迟时间和单位
service.schedule(runnable,3, TimeUnit.SECONDS);
//周期性执行:第2-4参数分别为:延迟执行时间,周期性执行时间,时间单位
service.scheduleAtFixedRate(runnable,1,2,TimeUnit.SECONDS);
6 小结
对线程池的初步了解就到这里,具体使用哪一个线程池还是得看具体需求,后期随着学习的深入再慢慢了解线程池的原理