通俗易懂的java线程池

文章目录

  • 池化技术
  • 使用篇
    • 简单版
      • newSingleThreadExecutor
        • 使用场景
        • 使用方式
      • newCachedThreadPool
        • 使用场景
        • 使用方式
      • newFixedThreadPool
        • 使用场景
        • 使用方式
      • newScheduledThreadPool
        • 使用场景
        • 使用方式
    • 进阶版
      • Executors源码分析
      • 使用方式
      • 参数说明
      • 关于handler
    • 高阶版
      • submit和execute
      • submit的使用
        • 简单demo
        • 通过Future创建守护线程
  • 小结

池化技术

在Java的并发编程中,线程的运用十分广泛,使用多线程可使项目的性能得到明显的改善,但是如果每次都是创建线程->执行任务->销毁线程,会造成很大的性能开销。
那么能否一个线程创建后,执行完一个任务后,又去执行另一个任务,而不是销毁。这也就是池化技术的思想,通过预先创建好多个线程,放在池中,这样可以在需要使用线程的时候直接获取,避免多次重复创建、销毁带来的开销。
在编程领域,比较典型的池化技术还有连接池、内存池、对象池等。

使用篇

简单版

Java.util.concurrent包下,Executors利用工厂模式,提供了4种线程池实现方式。

newSingleThreadExecutor

使用场景

单线程的线程池,这个线程池始终只有一个线程在工作。

使用方式

ExecutorService threedPool = Executors.newSingleThreadExecutor();

threedPool.execute(() -> {
     System.out.println("test");
});

newCachedThreadPool

使用场景

可缓存的线程池,调用execute 将重用以前构造的线程(如果线程可用)。如果没有可用的线程,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。

使用方式

ExecutorService threedPool = Executors.newCachedThreadPool();

threedPool.execute(() -> {
     System.out.println("test");
});

newFixedThreadPool

使用场景

创建固定数目线程的线程池。

使用方式

ExecutorService threedPool = Executors.newFixedThreadPool(100);

threedPool.execute(() -> {
     System.out.println("test");
});

newScheduledThreadPool

使用场景

创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类。

使用方式

ExecutorService threedPool = Executors.newScheduledThreadPool(100);

//延迟0秒,每一秒打印一句"test"
((ScheduledExecutorService) threedPool).scheduleAtFixedRate(() -> 
       System.out.println("test"),
       0,1000, TimeUnit.MILLISECONDS);

进阶版

阿里巴巴java开发手册有说明:
线程池不允许使用 Executors去创建,而是通过ThreadPoolExecutor 的方式,这样 的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors返回的线程池对象的弊端如下:
1)FixedThreadPoolSingleThreadPool: 允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM
2)CachedThreadPoolScheduledThreadPool: 允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM

Executors源码分析

//创建newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                 0L, TimeUnit.MILLISECONDS,
                                 new LinkedBlockingQueue<Runnable>());
}

//创建newCachedThreadPool
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
            60L, TimeUnit.SECONDS,
             new SynchronousQueue<Runnable>());

可以看出,其实Executors也是通过ThreadPoolExecutor来创建线程池,只是默认传递了部分参数,简化了创建线程池的步骤,但同时也使得开发人员不了解其中的运行规则,容易潜伏问题。

使用方式

new ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,long keepAliveTime, TimeUnit unit,BlockingQueue workQueue,RejectedExecutionHandler handler) 
//handler可以不传,此时使用内部默认handler
ExecutorService threedPool = new ThreadPoolExecutor(1, 1,1, TimeUnit.SECONDS,new LinkedBlockingQueue<>());
//打印"test"
threedPool.execute(()-> System.out.println("test")); 

参数说明

参数名 说明
corePoolSize int 线程池维护线程的最少数量
maximumPoolSize int 线程池维护线程的最大数量
keepAliveTime long 线程池维护线程所允许的空闲时间
unit TimeUnit 线程池维护线程所允许的空闲时间的单位
workQueue BlockingQueue 线程池所使用的缓冲队列
handler RejectedExecutionHandler 线程池对拒绝任务的处理策略

关于handler

其他参数都好理解,对于handler,或许有人会比较陌生。
假设有如下场景:线程池中的线程数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maximumPoolSize,那么此时便需要通过handler所指定的策略来处理此任务。

ThreadPoolExecutor.java:

    /**
     * The default rejected execution handler
     */
    private static final RejectedExecutionHandler defaultHandler =
        new AbortPolicy();

前面提到过,ThreadPoolExecutor有默认handler,通过源码可知是AbortPolicy(),除了AbortPolicy(),还有CallerRunsPolicy()DiscardOldestPolicy()DiscardPolicy()三种,说明如下:

名称 说明
AbortPolicy() 抛出java.util.concurrent.RejectedExecutionException异常
CallerRunsPolicy() 重试添加当前的任务,他会自动重复调用execute()方法
DiscardOldestPolicy() 抛出抛弃旧的任务
DiscardPolicy() 抛弃当前的任务

高阶版

submit和execute

启用线程池执行任务时,可以使用execute()也可以选择submit()execute()并没有返回值,一般用来执行比较简单的线程任务。如果业务较复杂的线程任务,推荐使用submit()submit()可以返回一个Future对象,通过此对象,可以得知没一个线程任务的执行结果。

submit的使用

简单demo

public class Demo{
	public static void main(String[] args) {
        //handler可以不传,此时使用内部默认handler
        ExecutorService threedPool = new ThreadPoolExecutor(1, 1,1, TimeUnit.SECONDS,new LinkedBlockingQueue<>());
        //打印"test"
        Future future = threedPool.submit(()-> System.out.println("test"));
        //通过future.isDone()判断任务此时执行情况
        if(future.isDone()) {
            System.out.println("任务完成");
        }else {
            System.out.println("任务未完成");
        }
        try {
            //休息两秒
            TimeUnit.SECONDS.sleep(2);
        } catch (Exception e) {
            e.printStackTrace();
        }

        //通过future.isDone()判断任务此时执行情况
        if(future.isDone()) {
            System.out.println("任务完成");
        }else {
            System.out.println("任务未完成");
        }
    }
}

执行结果:

任务未完成
test
任务完成

通过Future创建守护线程

对于十分重要的线程任务,一般可以考虑为它创建一个守护线程,一方面可以实时监测它们的执行状况,另一方面也方便销毁卡住的线程,以免资源浪费。

简单守护线程demo:

public class Demo{

	public static void main(String[] args) {
        //handler可以不传,此时使用内部默认handler
        ExecutorService threedPool = new ThreadPoolExecutor(1, 1,1, TimeUnit.SECONDS,new LinkedBlockingQueue<>());
        //打印"test"
        Future future = threedPool.submit(()-> {
            try {
		        //假设此方法执行时间需要2秒
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("test");
        });
        monitorThreadTime(0,future);
        //通过future.isDone()判断任务此时执行情况
        if(future.isDone()) {
            System.out.println("任务完成");
        }else {
            System.out.println("任务未完成");
            monitorThreadTime(0,future);
        }
    }

    /**
     * 守护线程
     * @param timeOutTime 线程守护时间
     * @param future future
     */
    private static void monitorThreadTime(int timeOutTime,Future<String> future){
        //执行守护线程
        new Thread(() -> {
            //等待timeOutTime的时间,然后调用执行结果,如果没有执行完,则试着去结束
            try {
                TimeUnit.MICROSECONDS.sleep(timeOutTime);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            //判断future是否执行完成,如果超时未完成,则试着结束线程
            if(!future.isDone()){
                //false表示不允许线程执行时中断,true表示允许
                System.out.println("执行线程销毁");
                future.cancel(true);
            }
        }).start();
    }
}

执行结果:

java.lang.InterruptedException: sleep interrupted
任务未完成
执行线程销毁
任务完成
	at java.lang.Thread.sleep(Native Method)test

	at java.lang.Thread.sleep(Thread.java:340)
	at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
	at com.ouyang.sericefeign.ServiceFeignApplication.lambda$main$0(ServiceFeignApplication.java:34)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)

因为在sleep过程中,守护线程强行结束了主线程,所以抛出java.lang.InterruptedException: sleep interrupted异常。

小结

对于业务简单,且调用次数不多的线程任务,我认为可以使用Executors创建线程池,如此可以简化开发难度,提高开发效率,业务也比较好维护。
对于复杂业务,建议开发人员根据具体开发需要,使用ThreadPoolExecutor构建符合自己项目需求的线程池,以免留下性能安全上的漏洞和隐患。

你可能感兴趣的:(菜鸟经验,入门专栏,线程池)