Java线程池
线程的缺点:
1.线程的创建它会开辟本地方法栈、JVM栈、程序计数器私有的内存,同时消耗的时候需要销毁以上三个区域,因此频繁的创建和销毁线程比较消耗系统的资源。
2.在任务量远远大于线程可以处理的任务量的时候,不能很好的拒绝任务。
所以就有了线程池:
使用池化的而技术来管理和使用线程。
线程池的优点
1.可以避免频繁的创建和销毁线程
2.可以更好的管理线程的个数和资源的个数。
3.线程池拥有更多的功能,比如线程池可以进行定时任务的执行。
4.线程池可以更友好的拒绝不能处理的任务。
线程池的6种创建方式
一共有7种创建方式
创建方式一:
创建固定个数的线程池:
package ThreadPoolDemo ; import java . util . concurrent . ExecutorService ; import java . util . concurrent . Executors ; /** * user:ypc; * date:2021-06-13; * time: 10:24; */ public class ThreadPoolDemo1 { public static void main ( String [] args ) { //创建一个固定个数的线程池 ExecutorService executorService = Executors . newFixedThreadPool ( 10 ); //执行任务 for ( int i = 0 ; i < 10 ; i ++) { executorService . execute ( new Runnable () { @Override public void run () { System . out . println ( "线程名" + Thread . currentThread (). getName ()); } }); } } }
那么如果执行次数大于10次呢?
线程池不会创建新的线程,它会复用之前的线程。
那么如果只执行两个任务呢?它创建了是10个线程还是两个线程呢?
我们可以使用 Jconsole 来看一看:
结果是只有2个线程被创建。
创建方式二:
创建带有缓存的线程池:
适用于短期有大量的任务的时候使用
public class ThreadPoolDemo2 { public static void main ( String [] args ) { //创建带缓存的线程池 ExecutorService executorService = Executors . newCachedThreadPool (); for ( int i = 0 ; i < 100 ; i ++) { executorService . execute ( new Runnable () { @Override public void run () { System . out . println ( Thread . currentThread (). getName ()); } }); } } }
方式三:
创建执行定时任务的线程池
package ThreadPoolDemo ; import java . util . Date ; import java . util . concurrent . Executors ; import java . util . concurrent . ScheduledExecutorService ; import java . util . concurrent . TimeUnit ; /** * user:ypc; * date:2021-06-13; * time: 11:32; */ public class ThreadPoolDemo3 { public static void main ( String [] args ) { ScheduledExecutorService scheduledExecutorService = Executors . newScheduledThreadPool ( 2 ); System . out . println ( "执行定时任务前的时间:" + new Date ()); scheduledExecutorService . scheduleAtFixedRate ( new Runnable () { @Override public void run () { System . out . println ( "执行任务的时间:" + new Date ()); } }, 1 , 2 , TimeUnit . SECONDS ); } }
执行任务的四个参数的意义:
参数1:延迟执行的任务
参数2:延迟一段时间后执行
参数3:定时任务执行的频率
参数4:配合前两个参数使用,是2、3参数的时间单位
还有两种执行的方法:
只会执行一次的方法:
第三种的执行方式:
那么这种的执行方式和第一种的执行方式有什么区别呢?
当在两种执行的方式中分别加上sleep()之后:
方式一:
方式三:
结论很明显了:
第一种方式是以上一个任务的开始时间+定时的时间作为当前任务的开始时间
第三种方式是以上一个任务的结束时间来作为当前任务的开始时间。
创建方式四:
package ThreadPoolDemo ; import java . util . Date ; import java . util . concurrent . Executors ; import java . util . concurrent . ScheduledExecutorService ; import java . util . concurrent . TimeUnit ; /** * user:ypc; * date:2021-06-13; * time: 12:38; */ public class ThreadPoolDemo4 { public static void main ( String [] args ) { //创建单个执行任务的线程池 ScheduledExecutorService scheduledExecutorService = Executors . newSingleThreadScheduledExecutor (); System . out . println ( "执行任务之前" + new Date ()); scheduledExecutorService . scheduleWithFixedDelay ( new Runnable () { @Override public void run () { System . out . println ( "我是SingleThreadSchedule" + new Date ()); } }, 3 , 1 , TimeUnit . SECONDS ); } }
创建方式五:
创建单个线程的线程池
package ThreadPoolDemo ; import java . util . concurrent . ExecutorService ; import java . util . concurrent . Executors ; /** * user:ypc; * date:2021-06-13; * time: 12:55; */ public class ThreadPoolDemo5 { public static void main ( String [] args ) { //创建单个线程的线程池 ExecutorService executorService = Executors . newSingleThreadExecutor (); for ( int i = 0 ; i < 20 ; i ++) { executorService . execute ( new Runnable () { @Override public void run () { System . out . println ( "线程名 " + Thread . currentThread (). getName ()); } }); } } }
创建单个线程池的作用是什么?
1.可以避免频繁创建和销毁线程所带来的性能的开销
2.它有任务队列,可以存储多余的任务
3.可以更好的管理任务
4.当有大量的任务不能处理的时候,可以友好的执行拒绝策略
创建方式六:
创建异步线程池根据当前CPU来创建对应个数的线程池
package ThreadPoolDemo ; import java . util . concurrent . ExecutorService ; import java . util . concurrent . Executors ; /** * user:ypc; * date:2021-06-13; * time: 13:12; */ public class ThreadPoolDemo6 { public static void main ( String [] args ) { ExecutorService executorService = Executors . newWorkStealingPool (); for ( int i = 0 ; i < 10 ; i ++) { executorService . execute ( new Runnable () { @Override public void run () { System . out . println ( "线程名" + Thread . currentThread (). getName ()); } }); } } }
运行结果为什么什么都没有呢?
看下面的异步与同步的区别就知道了。
加上这个
就可以输出结果了
线程池的第七种创建方式
前六种的创建方式有什么问题呢?
1.线程的数量不可控(比如带缓存的线程池)
2.工作任务量不可控(默认的任务队列的大小时Integer.MAX_VALUE),任务比较大肯会导致内存的溢出。
所以就可以使用下面的创建线程池的方式了:
package ThreadPoolDemo ; import java . util . concurrent . LinkedBlockingDeque ; import java . util . concurrent . ThreadFactory ; import java . util . concurrent . ThreadPoolExecutor ; import java . util . concurrent . TimeUnit ; /** * user:ypc; * date:2021-06-13; * time: 15:05; */ public class ThreadPoolDemo7 { private static int threadId = 0 ; public static void main ( String [] args ) { ThreadFactory threadFactory = new ThreadFactory () { @Override public Thread newThread ( Runnable r ) { Thread thread = new Thread ( r ); thread . setName ( "我是threadPool-" + ++ threadId ); return thread ; } }; ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor ( 3 , 3 , 100 , TimeUnit . MILLISECONDS , new LinkedBlockingDeque <>( 12 ), threadFactory , new ThreadPoolExecutor . AbortPolicy ()); for ( int i = 0 ; i < 15 ; i ++) { threadPoolExecutor . execute ( new Runnable () { @Override public void run () { System . out . println ( Thread . currentThread (). getName ()); } }); } } }
参数说明:
参数一:核心线程数|线程池正常情况下的线程 数量 参数二:最大线程数|当有大量的任务的时候可以创建的最多的线程数 参数三:最大线程的存活时间 参数四:配合参数三一起使用的表示参数三的时间单位 参数五:任务队列 参数六:线程工厂 参数七:决绝策略
注意事项:最大的线程数要大于等于核心的线程数
五种拒绝策略
为什么拒绝策略可以舍弃最新的任务或者最旧的任务呢?
因为LinkedBlockingDeque时FIFO的。
第五种:自定义的拒绝策略
ThreadPoolExecutor的执行方式
package ThreadPoolDemo ; import java . util . concurrent .*; /** * user:ypc; * date:2021-06-13; * time: 16:58; */ public class ThreadPoolDemo9 { public static void main ( String [] args ) throws ExecutionException , InterruptedException { ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor ( 3 , 4 , 100 , TimeUnit . MILLISECONDS , new LinkedBlockingDeque <>( 10 ), new ThreadPoolExecutor . DiscardOldestPolicy ()); //线程池的执行方式一 threadPoolExecutor . execute ( new Runnable () { @Override public void run () { System . out . println ( "使用了execute()执行了线程池" ); } }); //线程池的执行方式二 Future < String > futureTask = threadPoolExecutor . submit ( new Callable < String >() { @Override public String call () throws Exception { return "使用submit(new Callable<>())执行了线程池" ; } }); System . out . println ( futureTask . get ()); } }
无返回值的执行方式
有返回值的执行方式
ThreadPoolExecutor的执行流程
当任务量小于核心线程数的时候,ThreadPoolExecutor会创建线程来执行任务
当任务量大于核心的线程数的时候,并且没有空闲的线程时候,且当线程池的线程数小于最大线程数的时候,此时会将任务存
放到任务队列中
如果任务队列也被存满了,且最大线程数大于线程池的线程数的时候,会创建新的线程来执行任务。
如果线程池的线程数等于最大的线程数,并且任务队列也已经满了,就会执行拒绝策略。